summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java7
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java7
-rw-r--r--cmds/uinput/README.md5
-rw-r--r--core/api/current.txt53
-rw-r--r--core/api/system-current.txt63
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityThread.java8
-rw-r--r--core/java/android/app/AppOpsManager.java11
-rw-r--r--core/java/android/app/ApplicationStartInfo.java26
-rw-r--r--core/java/android/app/BroadcastStickyCache.java40
-rw-r--r--core/java/android/app/INotificationManager.aidl5
-rw-r--r--core/java/android/app/IUserSwitchObserver.aidl10
-rw-r--r--core/java/android/app/Notification.java240
-rw-r--r--core/java/android/app/NotificationManager.java20
-rw-r--r--core/java/android/app/ResourcesManager.java9
-rw-r--r--core/java/android/app/TaskInfo.java5
-rw-r--r--core/java/android/app/UiModeManager.java59
-rw-r--r--core/java/android/app/admin/SecurityLog.java17
-rw-r--r--core/java/android/app/admin/SecurityLogTags.logtags4
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManagerHelper.java15
-rw-r--r--core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java32
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java9
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java8
-rw-r--r--core/java/android/app/appfunctions/OWNERS1
-rw-r--r--core/java/android/app/appfunctions/TEST_MAPPING7
-rw-r--r--core/java/android/app/ui_mode_manager.aconfig11
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl9
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig16
-rw-r--r--core/java/android/content/om/FabricatedOverlay.java35
-rw-r--r--core/java/android/content/pm/PackageManager.java19
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig18
-rw-r--r--core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl34
-rw-r--r--core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl28
-rw-r--r--core/java/android/content/pm/verify/pkg/IVerifierService.aidl31
-rw-r--r--core/java/android/content/pm/verify/pkg/VerificationSession.aidl20
-rw-r--r--core/java/android/content/pm/verify/pkg/VerificationSession.java276
-rw-r--r--core/java/android/content/pm/verify/pkg/VerificationStatus.aidl20
-rw-r--r--core/java/android/content/pm/verify/pkg/VerificationStatus.java166
-rw-r--r--core/java/android/content/pm/verify/pkg/VerifierService.java113
-rw-r--r--core/java/android/content/res/flags.aconfig8
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java9
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl3
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java12
-rw-r--r--core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig7
-rw-r--r--core/java/android/net/vcn/VcnTransportInfo.java43
-rw-r--r--core/java/android/net/vcn/VcnUtils.java97
-rw-r--r--core/java/android/os/BatteryStats.java5
-rw-r--r--core/java/android/os/BinderProxy.java5
-rw-r--r--core/java/android/os/Build.java63
-rw-r--r--core/java/android/os/GraphicsEnvironment.java5
-rw-r--r--core/java/android/os/RemoteCallbackList.java6
-rw-r--r--core/java/android/os/UserManager.java121
-rw-r--r--core/java/android/provider/ContactsContract.java152
-rw-r--r--core/java/android/provider/Settings.java99
-rw-r--r--core/java/android/service/notification/NotificationAssistantService.java17
-rw-r--r--core/java/android/service/notification/ZenModeDiff.java380
-rw-r--r--core/java/android/service/notification/ZenPolicy.java6
-rw-r--r--core/java/android/text/flags/flags.aconfig10
-rw-r--r--core/java/android/tracing/TEST_MAPPING10
-rw-r--r--core/java/android/view/HandwritingInitiator.java7
-rw-r--r--core/java/android/view/SurfaceControl.java25
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/view/Window.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java88
-rw-r--r--core/java/android/window/WindowContainerTransaction.java43
-rw-r--r--core/java/android/window/flags/DesktopModeFlags.java3
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig14
-rw-r--r--core/java/com/android/internal/notification/SystemNotificationChannels.java59
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java18
-rw-r--r--core/java/com/android/internal/protolog/ProtoLog.java4
-rw-r--r--core/java/com/android/internal/protolog/TEST_MAPPING3
-rw-r--r--core/jni/Android.bp20
-rw-r--r--core/jni/android_database_CursorWindow.cpp84
-rw-r--r--core/jni/android_view_SurfaceControl.cpp12
-rw-r--r--core/jni/jni_wrappers.h21
-rw-r--r--core/jni/platform/host/HostRuntime.cpp6
-rw-r--r--core/res/AndroidManifest.xml24
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/strings.xml10
-rw-r--r--core/res/res/values/symbols.xml8
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java12
-rw-r--r--core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java155
-rw-r--r--core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java72
-rw-r--r--core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java92
-rw-r--r--core/tests/coretests/src/android/content/res/ResourcesManagerTest.java44
-rw-r--r--core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java107
-rw-r--r--core/tests/utiltests/Android.bp2
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java4
-rw-r--r--graphics/java/android/graphics/Canvas.java2
-rw-r--r--graphics/java/android/graphics/Paint.java46
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt153
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt207
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt)23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java11
-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/DesktopModeWindowDecoration.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt160
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt220
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt196
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt)24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt190
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt45
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java51
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt30
-rw-r--r--libs/androidfw/Android.bp7
-rw-r--r--libs/androidfw/CursorWindow.cpp2
-rw-r--r--libs/androidfw/include/androidfw/CursorWindow.h4
-rw-r--r--libs/appfunctions/api/current.txt6
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java9
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java12
-rw-r--r--libs/hwui/AutoBackendTextureRelease.cpp7
-rw-r--r--libs/hwui/Gainmap.cpp27
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp11
-rw-r--r--libs/hwui/hwui/Paint.h9
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp3
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp14
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp27
-rw-r--r--libs/hwui/jni/Paint.cpp33
-rw-r--r--libs/hwui/jni/graphics_jni_helpers.h45
-rw-r--r--media/java/android/media/RingtoneManager.java19
-rw-r--r--media/java/android/media/RoutingSessionInfo.java27
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java22
-rw-r--r--nfc/api/current.txt1
-rw-r--r--nfc/api/system-current.txt14
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl4
-rw-r--r--nfc/java/android/nfc/INfcOemExtensionCallback.aidl9
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java232
-rw-r--r--nfc/java/android/nfc/RoutingStatus.java79
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java114
-rw-r--r--nfc/java/android/nfc/flags.aconfig8
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml12
-rw-r--r--packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml31
-rw-r--r--packages/SettingsLib/ButtonPreference/res/values/attrs.xml4
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java4
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java16
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java12
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java33
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java4
-rw-r--r--packages/SettingsProvider/test/AndroidTest.xml4
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java4
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig32
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt23
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt27
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt123
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt212
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt242
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt66
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt89
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt24
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt68
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt24
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt105
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt120
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt55
-rw-r--r--packages/SystemUI/customization/Android.bp1
-rw-r--r--packages/SystemUI/customization/res/values/ids.xml9
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt51
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt448
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt21
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt288
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt30
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt104
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt64
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt32
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt117
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt180
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt260
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt654
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java132
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable (renamed from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt)3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ZigZagClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt323
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch (renamed from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt)3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt112
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt150
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt2
-rw-r--r--packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml26
-rw-r--r--packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml27
-rw-r--r--packages/SystemUI/res/values-night/styles.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml33
-rw-r--r--packages/SystemUI/res/values/styles.xml3
-rw-r--r--packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json88
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java66
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt162
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt131
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt204
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/SimLog.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt163
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/OWNERS7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt)27
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt123
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt)8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt195
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt)11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt25
-rw-r--r--packages/SystemUI/utils/kairos/Android.bp (renamed from packages/SystemUI/frp/Android.bp)8
-rw-r--r--packages/SystemUI/utils/kairos/OWNERS (renamed from packages/SystemUI/frp/OWNERS)0
-rw-r--r--packages/SystemUI/utils/kairos/README.md (renamed from packages/SystemUI/frp/README.md)8
-rw-r--r--packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md (renamed from packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md)26
-rw-r--r--packages/SystemUI/utils/kairos/docs/semantics.md (renamed from packages/SystemUI/frp/docs/semantics.md)6
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt)12
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt)32
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt)60
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt)46
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt)14
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt)38
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt)54
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt)6
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt)12
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt)28
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt)10
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt)4
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt)18
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt)44
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt)28
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt)16
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt)4
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt)4
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt)4
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt)40
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt)4
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt)20
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt)6
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt)8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt (renamed from packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt)2
-rw-r--r--packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt (renamed from packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt)24
-rw-r--r--proto/src/system_messages.proto2
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java5
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java11
-rw-r--r--ravenwood/tests/bivalenttest/Android.bp (renamed from ravenwood/bivalenttest/Android.bp)58
-rw-r--r--ravenwood/tests/bivalenttest/AndroidManifest.xml (renamed from ravenwood/bivalenttest/AndroidManifest.xml)0
-rw-r--r--ravenwood/tests/bivalenttest/AndroidTest.xml (renamed from ravenwood/bivalenttest/AndroidTest.xml)0
-rw-r--r--ravenwood/tests/bivalenttest/README.md (renamed from ravenwood/bivalenttest/README.md)0
-rw-r--r--ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp (renamed from ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java)0
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java (renamed from ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java)0
-rw-r--r--ravenwood/tests/coretest/Android.bp6
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java35
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt18
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt2
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt6
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt16
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java4
-rw-r--r--services/appfunctions/TEST_MAPPING5
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java78
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java4
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java6
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java103
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java13
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java10
-rw-r--r--services/core/java/com/android/server/RescueParty.java13
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java37
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java6
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java8
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig12
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java26
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java8
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java12
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java28
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java97
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java42
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java4
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java32
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java7
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java12
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig10
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java3
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java146
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java50
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java31
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java63
-rw-r--r--services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java28
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java19
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java183
-rw-r--r--services/core/java/com/android/server/notification/NotificationSignalExtractor.java5
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java4
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java108
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java6
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java9
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java100
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java40
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java11
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java4
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java9
-rw-r--r--services/core/java/com/android/server/power/Notifier.java2
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java13
-rw-r--r--services/core/java/com/android/server/power/hint/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java46
-rw-r--r--services/core/java/com/android/server/vibrator/HalVibration.java81
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java173
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java49
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java41
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStats.java42
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java41
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java424
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java48
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java5
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java5
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java12
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraPolicy.java116
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java2
-rw-r--r--services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java5
-rw-r--r--services/core/java/com/android/server/wm/DimmerAnimationHelper.java2
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java17
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java6
-rw-r--r--services/core/java/com/android/server/wm/Transition.java3
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java7
-rw-r--r--services/core/java/com/android/server/wm/TrustedPresentationListenerController.java40
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java53
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java7
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd6
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java61
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt5
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java2
-rw-r--r--services/tests/appfunctions/Android.bp1
-rw-r--r--services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt35
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java45
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java19
-rw-r--r--services/tests/servicestests/Android.bp14
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java80
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java24
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java55
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java169
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java57
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java126
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java491
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java46
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java76
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java22
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java46
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java200
-rw-r--r--services/usb/java/com/UsbDataSignalDisableRequesters.java36
-rw-r--r--services/usb/java/com/android/server/usb/UsbManagerInternal.java48
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java93
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java19
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java62
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl11
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java23
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java146
-rw-r--r--tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java23
-rw-r--r--tests/vcn/java/android/net/vcn/VcnUtilsTest.java136
-rw-r--r--tools/aapt/StringPool.cpp2
-rw-r--r--tools/aapt2/Debug.cpp2
-rw-r--r--tools/processors/property_cache/Android.bp57
-rw-r--r--tools/processors/property_cache/TEST_MAPPING7
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java138
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java40
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java95
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java40
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java152
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java103
-rw-r--r--tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java64
-rw-r--r--tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java134
-rw-r--r--tools/processors/property_cache/test/resources/Custom.java127
-rw-r--r--tools/processors/property_cache/test/resources/CustomCache.java384
-rw-r--r--tools/processors/property_cache/test/resources/Default.java125
-rw-r--r--tools/processors/property_cache/test/resources/DefaultCache.java402
666 files changed, 19901 insertions, 4571 deletions
diff --git a/Android.bp b/Android.bp
index 516fc9c55222..811755d0bdaf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -368,6 +368,7 @@ java_defaults {
jarjar_rules: ":framework-jarjar-rules",
javac_shard_size: 150,
plugins: [
+ "cached-property-annotation-processor",
"view-inspector-annotation-processor",
"staledataclass-annotation-processor",
"error_prone_android_framework",
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 238c028fa0cf..9eac10846319 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -18,6 +18,7 @@ package android.libcore.regression;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
import androidx.test.filters.LargeTest;
@@ -47,6 +48,8 @@ import javax.crypto.spec.IvParameterSpec;
@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CipherPerfTest {
+ private static final String TAG = "android.libcore.regression.CipherPerfTest";
+
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public static Collection getCases() {
@@ -71,6 +74,10 @@ public class CipherPerfTest {
}
for (int keySize : keySizes) {
for (int inputSize : inputSizes) {
+ Log.i(TAG,
+ "param[" + params.size() + "] = " + mode.name() + ", "
+ + padding.name() + ", " + keySize + ", " + inputSize
+ + ", " + implementation.name());
params.add(
new Object[] {
mode, padding, keySize, inputSize, implementation
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 52a761f8d486..31d2ecdb83b0 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -34,6 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.Process;
import android.system.SystemCleaner;
import android.util.Log;
@@ -638,6 +639,12 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void enableCleaner() {
+ // JobParameters objects are passed by reference in local Binder
+ // transactions for clients running as SYSTEM. The life cycle of the
+ // JobParameters objects are no longer controlled by the client.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return;
+ }
if (mJobCleanupCallback == null) {
initCleaner(new JobCleanupCallback(IJobCallback.Stub.asInterface(callback), jobId));
}
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 5d3f12e0d59f..6138388b30c7 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -83,6 +83,11 @@ control values to be sent to the uinput device, which depends on the control cod
Due to the sequential nature in which this is parsed, the `type` field must be specified before
the `data` field in this JSON Object.
+Every `register` command will need a `"UI_SET_EVBIT"` configuration entry that lists what types of
+axes it declares. This entry should be the first in the list. For example, if the uinput device has
+`"UI_SET_KEYBIT"` and `"UI_SET_RELBIT"` configuration entries, it will also need a `"UI_SET_EVBIT"`
+entry with data of `["EV_KEY", "EV_REL"]` or the other configuration entries will be ignored.
+
`ff_effects_max` must be provided if `UI_SET_FFBIT` is used in `configuration`.
`abs_info` fields are provided to set the device axes information. It is an array of below objects:
diff --git a/core/api/current.txt b/core/api/current.txt
index 67b328057efe..b740ef36debe 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6858,43 +6858,43 @@ package android.app {
@FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
ctor public Notification.ProgressStyle();
+ method @NonNull public android.app.Notification.ProgressStyle addProgressPoint(@NonNull android.app.Notification.ProgressStyle.Point);
method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
- method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
method public int getProgress();
method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
method public int getProgressMax();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Point> getProgressPoints();
method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
- method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
method public boolean isProgressIndeterminate();
method public boolean isStyledByProgress();
method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressPoints(@NonNull java.util.List<android.app.Notification.ProgressStyle.Point>);
method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
- method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
}
+ public static final class Notification.ProgressStyle.Point {
+ ctor public Notification.ProgressStyle.Point(int);
+ method @ColorInt public int getColor();
+ method public int getId();
+ method public int getPosition();
+ method @NonNull public android.app.Notification.ProgressStyle.Point setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Point setId(int);
+ }
+
public static final class Notification.ProgressStyle.Segment {
ctor public Notification.ProgressStyle.Segment(int);
method @ColorInt public int getColor();
+ method public int getId();
method public int getLength();
- method public int getStableId();
method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int);
- }
-
- public static final class Notification.ProgressStyle.Step {
- ctor public Notification.ProgressStyle.Step(int);
- method @ColorInt public int getColor();
- method public int getPosition();
- method public int getStableId();
- method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
- method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int);
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setId(int);
}
public abstract static class Notification.Style {
@@ -8673,6 +8673,8 @@ package android.app.admin {
field public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = 210019; // 0x33463
field public static final int TAG_MEDIA_MOUNT = 210013; // 0x3345d
field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e
+ field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_DISABLED = 210046; // 0x3347e
+ field @FlaggedApi("android.nfc.nfc_state_change_security_log_event_enabled") public static final int TAG_NFC_ENABLED = 210045; // 0x3347d
field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a
field public static final int TAG_OS_STARTUP = 210009; // 0x33459
field public static final int TAG_PACKAGE_INSTALLED = 210041; // 0x33479
@@ -8787,7 +8789,7 @@ package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8822,13 +8824,12 @@ package android.app.appfunctions {
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
- field public static final int RESULT_CANCELLED = 7; // 0x7
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
@@ -12114,6 +12115,7 @@ package android.content.om {
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.dimension_frro") public void setResourceValue(@NonNull String, float, int, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -32821,7 +32823,7 @@ package android.os {
field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
field @Deprecated public static final String SDK;
field public static final int SDK_INT;
- field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_MINOR_INT;
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_INT_FULL;
field public static final String SECURITY_PATCH;
}
@@ -32865,6 +32867,9 @@ package android.os {
field public static final int VANILLA_ICE_CREAM = 35; // 0x23
}
+ @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL {
+ }
+
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
ctor public Bundle();
ctor public Bundle(ClassLoader);
@@ -36989,7 +36994,7 @@ package android.provider {
}
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
- ctor public ContactsContract.RawContacts.DefaultAccount();
+ method @FlaggedApi("android.provider.new_default_account_api_enabled") @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState getDefaultAccountForNewContacts(@NonNull android.content.ContentResolver);
}
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
@@ -37429,6 +37434,7 @@ package android.provider {
field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
+ field @FlaggedApi("android.app.api_rich_ongoing") public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
@@ -54956,6 +54962,7 @@ package android.view.accessibility {
method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public void addHighContrastTextStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
method @ColorInt public int getAccessibilityFocusColor();
@@ -54968,12 +54975,14 @@ package android.view.accessibility {
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public boolean isHighContrastTextEnabled();
method public boolean isRequestFromAccessibilityTool();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
+ method @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public void removeHighContrastTextStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -54993,6 +55002,10 @@ package android.view.accessibility {
method public void onAudioDescriptionRequestedChanged(boolean);
}
+ @FlaggedApi("com.android.graphics.hwui.flags.high_contrast_text_small_text_rect") public static interface AccessibilityManager.HighContrastTextStateChangeListener {
+ method public void onHighContrastTextStateChanged(boolean);
+ }
+
public static interface AccessibilityManager.TouchExplorationStateChangeListener {
method public void onTouchExplorationStateChanged(boolean);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 20bcf5fdf6fb..7e43e4664d8a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -91,6 +91,7 @@ package android {
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String BIND_VERIFICATION_AGENT = "android.permission.BIND_VERIFICATION_AGENT";
field public static final String BIND_VISUAL_QUERY_DETECTION_SERVICE = "android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE";
field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
@@ -412,6 +413,7 @@ package android {
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String VERIFICATION_AGENT = "android.permission.VERIFICATION_AGENT";
field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String VIBRATE_VENDOR_EFFECTS = "android.permission.VIBRATE_VENDOR_EFFECTS";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -4303,6 +4305,7 @@ package android.content.pm {
method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_APK = 1; // 0x1
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_INSTALLER = 2; // 0x2
field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3; // 0x3
@@ -4616,6 +4619,61 @@ package android.content.pm.verify.domain {
}
+package android.content.pm.verify.pkg {
+
+ @FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
+ method public int describeContents();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+ method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
+ method @NonNull public android.os.PersistableBundle getExtensionParams();
+ method public int getId();
+ method public int getInstallSessionId();
+ method @NonNull public String getPackageName();
+ method @NonNull public android.content.pm.SigningInfo getSigningInfo();
+ method @NonNull public android.net.Uri getStagedPackageUri();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
+ field public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; // 0x2
+ field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
+ field public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0; // 0x0
+ }
+
+ @FlaggedApi("android.content.pm.verification_service") public final class VerificationStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAslStatus();
+ method @NonNull public String getFailureMessage();
+ method public boolean isVerified();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationStatus> CREATOR;
+ field public static final int VERIFIER_STATUS_ASL_BAD = 2; // 0x2
+ field public static final int VERIFIER_STATUS_ASL_GOOD = 1; // 0x1
+ field public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class VerificationStatus.Builder {
+ ctor public VerificationStatus.Builder();
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus build();
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setAslStatus(int);
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setFailureMessage(@NonNull String);
+ method @NonNull public android.content.pm.verify.pkg.VerificationStatus.Builder setVerified(boolean);
+ }
+
+ @FlaggedApi("android.content.pm.verification_service") public abstract class VerifierService extends android.app.Service {
+ ctor public VerifierService();
+ method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public abstract void onPackageNameAvailable(@NonNull String);
+ method public abstract void onVerificationCancelled(@NonNull String);
+ method public abstract void onVerificationRequired(@NonNull android.content.pm.verify.pkg.VerificationSession);
+ method public abstract void onVerificationRetry(@NonNull android.content.pm.verify.pkg.VerificationSession);
+ method public abstract void onVerificationTimeout(int);
+ }
+
+}
+
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
@@ -11892,6 +11950,10 @@ package android.provider {
field @Deprecated public static final String STATE = "state";
}
+ @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount {
+ method @FlaggedApi("android.provider.new_default_account_api_enabled") @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccountForNewContacts(@NonNull android.content.ContentResolver, @NonNull android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState);
+ }
+
public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
}
@@ -13046,6 +13108,7 @@ package android.service.notification {
method public void onPanelHidden();
method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+ method @FlaggedApi("android.service.notification.notification_classification") public final void setAdjustmentTypeSupportedState(@NonNull String, boolean);
method public final void unsnoozeNotification(@NonNull String);
field public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
field @FlaggedApi("android.service.notification.notification_classification") public static final String ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f32d805e6f5d..9bcdf959a6a7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -396,6 +396,7 @@ package android.app {
method public void cleanUpCallersAfter(long);
method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
method public android.content.ComponentName getEffectsSuppressor();
+ method @FlaggedApi("android.service.notification.notification_classification") @NonNull public java.util.Set<java.lang.String> getUnsupportedAdjustmentTypes();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5b556cc22640..95d3ea51c4d3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1960,8 +1960,12 @@ public final class ActivityThread extends ClientTransactionHandler
@Override
public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
- PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
- IoUtils.closeQuietly(pfd);
+ try {
+ PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+ BroadcastStickyCache.dump(pfd);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
}
private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f27dc322a2b7..5907af0904ad 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10153,6 +10153,9 @@ public class AppOpsManager {
}
p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+ final int sizePosition = p.dataPosition();
+ // Write size placeholder. With this size we can easily skip it in native.
+ p.writeInt(0);
int numAttributionWithNotesAppOps = notedAppOps.size();
p.writeInt(numAttributionWithNotesAppOps);
@@ -10169,6 +10172,12 @@ public class AppOpsManager {
}
}
}
+
+ final int payloadPosition = p.dataPosition();
+ p.setDataPosition(sizePosition);
+ // Total header size including 4 bytes size itself.
+ p.writeInt(payloadPosition - sizePosition);
+ p.setDataPosition(payloadPosition);
}
/**
@@ -10182,6 +10191,8 @@ public class AppOpsManager {
* @hide
*/
public static void readAndLogNotedAppops(@NonNull Parcel p) {
+ // Skip size.
+ p.readInt();
int numAttributionsWithNotedAppOps = p.readInt();
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index edcdb6cc58ea..f34341fd14e1 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -34,6 +34,7 @@ import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -777,7 +778,9 @@ public final class ApplicationStartInfo implements Parcelable {
mStartComponent = other.mStartComponent;
}
- private ApplicationStartInfo(@NonNull Parcel in) {
+ /** @hide */
+ @VisibleForTesting
+ public ApplicationStartInfo(@NonNull Parcel in) {
mStartupState = in.readInt();
mPid = in.readInt();
mRealUid = in.readInt();
@@ -1061,12 +1064,21 @@ public final class ApplicationStartInfo implements Parcelable {
if (other == null || !(other instanceof ApplicationStartInfo)) {
return false;
}
+
final ApplicationStartInfo o = (ApplicationStartInfo) other;
- return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
- && mDefiningUid == o.mDefiningUid && mReason == o.mReason
- && mStartupState == o.mStartupState && mStartType == o.mStartType
- && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
- && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped
+
+ return mPid == o.mPid
+ && mRealUid == o.mRealUid
+ && mPackageUid == o.mPackageUid
+ && mDefiningUid == o.mDefiningUid
+ && mReason == o.mReason
+ && mStartupState == o.mStartupState
+ && mStartType == o.mStartType
+ && mLaunchMode == o.mLaunchMode
+ && TextUtils.equals(mPackageName, o.mPackageName)
+ && TextUtils.equals(mProcessName, o.mProcessName)
+ && timestampsEquals(o)
+ && mWasForceStopped == o.mWasForceStopped
&& mMonoticCreationTimeMs == o.mMonoticCreationTimeMs
&& mStartComponent == o.mStartComponent;
}
@@ -1074,7 +1086,7 @@ public final class ApplicationStartInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
- mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs,
+ mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
mMonoticCreationTimeMs, mStartComponent);
}
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
index d6f061be3b00..ea8173191a3f 100644
--- a/core/java/android/app/BroadcastStickyCache.java
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -27,16 +27,21 @@ import android.net.TetheringManager;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
+import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.os.UpdateLock;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.view.WindowManagerPolicyConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
/** @hide */
@@ -214,6 +219,41 @@ public class BroadcastStickyCache {
}
}
+ public static void dump(@NonNull ParcelFileDescriptor pfd) {
+ if (!Flags.useStickyBcastCache()) {
+ return;
+ }
+ final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
+ synchronized (sCachedStickyBroadcasts) {
+ dumpLocked(pw);
+ }
+ pw.flush();
+ }
+
+ @GuardedBy("sCachedStickyBroadcasts")
+ private static void dumpLocked(@NonNull PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(
+ pw, " " /* singleIndent */, " " /* prefix */);
+ ipw.println("Cached sticky broadcasts:");
+ ipw.increaseIndent();
+ final int count = sCachedStickyBroadcasts.size();
+ if (count == 0) {
+ ipw.println("<empty>");
+ } else {
+ for (int i = 0; i < count; ++i) {
+ final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+ ipw.print("Entry #"); ipw.print(i); ipw.println(":");
+ ipw.increaseIndent();
+ ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
+ ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
+ ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
+ ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
private static final class CachedStickyBroadcast {
@NonNull public final IntentFilter filter;
@Nullable public Intent intent;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8a54b5d4ec4f..3b2aab4591a5 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -160,8 +160,8 @@ interface INotificationManager
void requestBindProvider(in ComponentName component);
void requestUnbindProvider(in IConditionProvider token);
- void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
+ void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
ParceledListSlice getSnoozedNotificationsFromListener(in INotificationListener token, int trim);
void clearRequestedListenerHints(in INotificationListener token);
@@ -261,4 +261,7 @@ interface INotificationManager
void setCanBePromoted(String pkg, int uid, boolean promote, boolean fromUser);
boolean appCanBePromoted(String pkg, int uid);
boolean canBePromoted(String pkg);
+
+ void setAdjustmentTypeSupportedState(in INotificationListener token, String key, boolean supported);
+ List<String> getUnsupportedAdjustmentTypes();
}
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index cfdb426d6026..1ff7a17e578f 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -19,10 +19,10 @@ package android.app;
import android.os.IRemoteCallback;
/** {@hide} */
-oneway interface IUserSwitchObserver {
+interface IUserSwitchObserver {
void onBeforeUserSwitching(int newUserId);
- void onUserSwitching(int newUserId, IRemoteCallback reply);
- void onUserSwitchComplete(int newUserId);
- void onForegroundProfileSwitch(int newProfileId);
- void onLockedBootComplete(int newUserId);
+ oneway void onUserSwitching(int newUserId, IRemoteCallback reply);
+ oneway void onUserSwitchComplete(int newUserId);
+ oneway void onForegroundProfileSwitch(int newProfileId);
+ oneway void onLockedBootComplete(int newUserId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e8b0a36ffcfc..38632bdeeff5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -809,6 +809,10 @@ public class Notification implements Parcelable
return false;
}
+ private static boolean isStandardLayout(int layoutId) {
+ return STANDARD_LAYOUTS.contains(layoutId);
+ }
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
FLAG_SHOW_LIGHTS,
@@ -1637,16 +1641,16 @@ public class Notification implements Parcelable
public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
/**
- * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+ * {@link #extras} key: an arraylist of {@link ProgressStyle.Point}
* bundles provided by a
* {@link android.app.Notification.ProgressStyle} notification as supplied to
- * {@link ProgressStyle#setProgressSteps}
- * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+ * {@link ProgressStyle#setProgressPoints}
+ * or {@link ProgressStyle#addProgressPoint(ProgressStyle.Point)}.
* This extra is a parcelable array list of bundles.
* @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+ public static final String EXTRA_PROGRESS_POINTS = "android.progressPoints";
/**
* {@link #extras} key: whether the progress bar should be styled by its progress as
@@ -5983,9 +5987,9 @@ public class Notification implements Parcelable
}
}
boolean contentViewUsesHeader = mN.contentView == null
- || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
+ || isStandardLayout(mN.contentView.getLayoutId());
boolean bigContentViewUsesHeader = mN.bigContentView == null
- || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
+ || isStandardLayout(mN.bigContentView.getLayoutId());
return contentViewUsesHeader && bigContentViewUsesHeader;
}
@@ -6781,7 +6785,7 @@ public class Notification implements Parcelable
return false;
}
if (fullyCustomViewRequiresDecoration(false)
- && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
+ && isStandardLayout(customContent.getLayoutId())) {
// If the app's custom views are objects returned from Builder.create*ContentView()
// then the app is most likely attempting to spoof the user. Even if they are not,
// the result would be broken (b/189189308) so we will ignore it.
@@ -11159,7 +11163,7 @@ public class Notification implements Parcelable
/**
* A Notification Style used to to define a notification whose expanded state includes
- * a highly customizable progress bar with segments, steps, a custom tracker icon,
+ * a highly customizable progress bar with segments, points, a custom tracker icon,
* and custom icons at the start and end of the progress bar.
*
* This style is suggested for use cases where the app is showing a tracker to the
@@ -11185,8 +11189,8 @@ public class Notification implements Parcelable
* .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
* .addProgressSegment(new Segment(94).setColor(Color.BLUE))
- * .addProgressStep(new Step(60).setColor(Color.RED))
- * .addProgressStep(new Step(560).setColor(Color.YELLOW))
+ * .addProgressPoint(new Point(60).setColor(Color.RED))
+ * .addProgressPoint(new Point(560).setColor(Color.YELLOW))
* )
* </pre>
*
@@ -11199,17 +11203,17 @@ public class Notification implements Parcelable
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public static class ProgressStyle extends Notification.Style {
- private static final String KEY_ELEMENT_STABLE_ID = "stableId";
+ private static final String KEY_ELEMENT_ID = "id";
private static final String KEY_ELEMENT_COLOR = "colorInt";
private static final String KEY_SEGMENT_LENGTH = "length";
- private static final String KEY_STEP_POSITION = "position";
+ private static final String KEY_POINT_POSITION = "position";
private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
- private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+ private static final int MAX_PROGRESS_STOP_LIMIT = 5;
private static final int DEFAULT_PROGRESS_MAX = 100;
private List<Segment> mProgressSegments = new ArrayList<>();
- private List<Step> mProgressSteps = new ArrayList<>();
+ private List<Point> mProgressPoints = new ArrayList<>();
private int mProgress = 0;
@@ -11246,7 +11250,7 @@ public class Notification implements Parcelable
nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
|| !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
|| !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
- || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+ || !Objects.equals(mProgressPoints, progressStyle.mProgressPoints)
|| !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
}
@@ -11300,48 +11304,47 @@ public class Notification implements Parcelable
}
/**
- * Gets the steps that are displayed on the progress bar.
+ * Gets the points that are displayed on the progress bar.
*.
- * @see #setProgressSteps
- * @see #addProgressStep
- * @see Step
+ * @see #setProgressPoints
+ * @see #addProgressPoint
+ * @see Point
*/
- public @NonNull List<Step> getProgressSteps() {
- return mProgressSteps;
+ public @NonNull List<Point> getProgressPoints() {
+ return mProgressPoints;
}
/**
- * Replaces all the progress steps.
+ * Replaces all the progress points.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
- * @see Step
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ * @see Point
*/
- public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
- mProgressSteps = new ArrayList<>(steps);
+ public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
+ mProgressPoints = new ArrayList<>(points);
return this;
}
/**
- * Adds another step.
+ * Adds another point.
*
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
*
- * Steps can be added in any order, as their
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
+ *
+ * Points can be added in any order, as their
* position within the progress bar is determined by their individual
- * {@link Step#getPosition()}.
- * @see Step
+ * {@link Point#getPosition()}.
+ * @see Point
*/
- public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
- if (mProgressSteps == null) {
- mProgressSteps = new ArrayList<>();
+ public @NonNull ProgressStyle addProgressPoint(@NonNull Point point) {
+ if (mProgressPoints == null) {
+ mProgressPoints = new ArrayList<>();
}
- mProgressSteps.add(step);
+ mProgressPoints.add(point);
return this;
}
@@ -11414,7 +11417,7 @@ public class Notification implements Parcelable
* When specified, the following fields are ignored:
* @see #setProgress
* @see #setProgressSegments
- * @see #setProgressSteps
+ * @see #setProgressPoints
* @see #setProgressTrackerIcon
* @see #setStyledByProgress
*
@@ -11435,7 +11438,7 @@ public class Notification implements Parcelable
}
/**
- * Indicates whether the segments and steps will be styled differently
+ * Indicates whether the segments and points will be styled differently
* based on whether they are behind or ahead of the current progress.
* When true, segments appearing ahead of the current progress will be given a
* slightly different appearance to indicate that it is part of the progress bar
@@ -11558,8 +11561,8 @@ public class Notification implements Parcelable
super.addExtras(extras);
extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
getProgressSegmentsAsBundleList(mProgressSegments));
- extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
- getProgressStepsAsBundleList(mProgressSteps));
+ extras.putParcelableArrayList(EXTRA_PROGRESS_POINTS,
+ getProgressPointsAsBundleList(mProgressPoints));
extras.putInt(EXTRA_PROGRESS, mProgress);
extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
@@ -11599,8 +11602,8 @@ public class Notification implements Parcelable
mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
- mProgressSteps = getProgressStepsFromBundleList(
- extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
+ mProgressPoints = getProgressPointsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_POINTS, Bundle.class));
}
/**
@@ -11613,6 +11616,30 @@ public class Notification implements Parcelable
// actually be included.
return true;
}
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+ }
+ /**
+ * @hide
+ */
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+
+ return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
+ }
private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
@Nullable List<Segment> progressSegments) {
@@ -11626,7 +11653,7 @@ public class Notification implements Parcelable
final Bundle bundle = new Bundle();
bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId());
+ bundle.putInt(KEY_ELEMENT_ID, segment.getId());
bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
segments.add(bundle);
@@ -11647,11 +11674,11 @@ public class Notification implements Parcelable
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int id = segmentBundle.getInt(KEY_ELEMENT_ID);
final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
final Segment segment = new Segment(length)
- .setStableId(stableId).setColor(color);
+ .setId(id).setColor(color);
segments.add(segment);
}
@@ -11660,48 +11687,48 @@ public class Notification implements Parcelable
return segments;
}
- private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
- @Nullable List<Step> progressSteps) {
- final ArrayList<Bundle> steps = new ArrayList<>();
- if (progressSteps != null && !progressSteps.isEmpty()) {
- for (int i = 0; i < progressSteps.size(); i++) {
- final Step step = progressSteps.get(i);
- if (step.getPosition() < 0) {
+ private static @NonNull ArrayList<Bundle> getProgressPointsAsBundleList(
+ @Nullable List<Point> progressPoints) {
+ final ArrayList<Bundle> points = new ArrayList<>();
+ if (progressPoints != null && !progressPoints.isEmpty()) {
+ for (int i = 0; i < progressPoints.size(); i++) {
+ final Point point = progressPoints.get(i);
+ if (point.getPosition() < 0) {
continue;
}
final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STEP_POSITION, step.getPosition());
- bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId());
- bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+ bundle.putInt(KEY_POINT_POSITION, point.getPosition());
+ bundle.putInt(KEY_ELEMENT_ID, point.getId());
+ bundle.putInt(KEY_ELEMENT_COLOR, point.getColor());
- steps.add(bundle);
+ points.add(bundle);
}
}
- return steps;
+ return points;
}
- private static @NonNull List<Step> getProgressStepsFromBundleList(
- @Nullable List<Bundle> stepBundleList) {
- final ArrayList<Step> steps = new ArrayList<>();
+ private static @NonNull List<Point> getProgressPointsFromBundleList(
+ @Nullable List<Bundle> pointBundleList) {
+ final ArrayList<Point> points = new ArrayList<>();
- if (stepBundleList != null && !stepBundleList.isEmpty()) {
- for (int i = 0; i < stepBundleList.size(); i++) {
- final Bundle segmentBundle = stepBundleList.get(i);
- final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+ if (pointBundleList != null && !pointBundleList.isEmpty()) {
+ for (int i = 0; i < pointBundleList.size(); i++) {
+ final Bundle pointBundle = pointBundleList.get(i);
+ final int position = pointBundle.getInt(KEY_POINT_POSITION);
if (position < 0) {
continue;
}
- final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
- final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ final int id = pointBundle.getInt(KEY_ELEMENT_ID);
+ final int color = pointBundle.getInt(KEY_ELEMENT_COLOR,
Notification.COLOR_DEFAULT);
- final Step step = new Step(position).setStableId(stableId).setColor(color);
- steps.add(step);
+ final Point point = new Point(position).setId(id).setColor(color);
+ points.add(point);
}
}
- return steps;
+ return points;
}
/**
@@ -11712,7 +11739,7 @@ public class Notification implements Parcelable
*/
public static final class Segment {
private int mLength;
- private int mStableId = 0;
+ private int mId = 0;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
@@ -11735,19 +11762,19 @@ public class Notification implements Parcelable
}
/**
- * Gets the stable id of this Segment.
+ * Gets the id of this Segment.
*
- * @see #setStableId
+ * @see #setId
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
* Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Segment setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Segment setId(int id) {
+ mId = id;
return this;
}
@@ -11776,45 +11803,44 @@ public class Notification implements Parcelable
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Segment segment = (Segment) o;
- return mLength == segment.mLength && mStableId == segment.mStableId
+ final Segment segment = (Segment) o;
+ return mLength == segment.mLength && mId == segment.mId
&& mColor == segment.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mLength, mStableId, mColor);
+ return Objects.hash(mLength, mId, mColor);
}
}
/**
- * A step within the progress bar, defining its position and color.
- * Steps are designated points within a progressbar to visualize
- * distinct stages or milestones.
- * For example, you might use steps to mark stops in a multi-stop
- * navigation journey, where each step represents a destination.
+ * A point within the progress bar, defining its position and color.
+ * Points within a progress bar are used to visualize distinct stages or milestones.
+ * For example, you might use points to mark stops in a multi-stop
+ * navigation journey, where each point represents a destination.
*/
- public static final class Step {
+ public static final class Point {
private int mPosition;
- private int mStableId;
+ private int mId;
@ColorInt
private int mColor = Notification.COLOR_DEFAULT;
/**
- * Create a step element.
- * The position of this step on the progress bar
+ * Create a point element.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}
* @param position
* See {@link #getPosition}
*/
- public Step(int position) {
+ public Point(int position) {
mPosition = position;
}
/**
- * Gets the position of this Step.
- * The position of this step on the progress bar
+ * Gets the position of this Point.
+ * The position of this point on the progress bar
* relative to {@link ProgressStyle#getProgressMax}.
*/
public int getPosition() {
@@ -11823,17 +11849,17 @@ public class Notification implements Parcelable
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public int getStableId() {
- return mStableId;
+ public int getId() {
+ return mId;
}
/**
- * Optional ID used to uniqurely identify the element across updates.
+ * Optional ID used to uniquely identify the element across updates.
*/
- public @NonNull Step setStableId(int stableId) {
- mStableId = stableId;
+ public @NonNull Point setId(int id) {
+ mId = id;
return this;
}
@@ -11850,7 +11876,7 @@ public class Notification implements Parcelable
/**
* Optional color of this Segment
*/
- public @NonNull Step setColor(@ColorInt int color) {
+ public @NonNull Point setColor(@ColorInt int color) {
mColor = color;
return this;
}
@@ -11862,14 +11888,14 @@ public class Notification implements Parcelable
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Step step = (Step) o;
- return mPosition == step.mPosition && mStableId == step.mStableId
- && mColor == step.mColor;
+ final Point point = (Point) o;
+ return mPosition == point.mPosition && mId == point.mId
+ && mColor == point.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mPosition, mStableId, mColor);
+ return Objects.hash(mPosition, mId, mColor);
}
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index c7b84ae6283b..41abd68c9924 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -68,9 +68,11 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -956,6 +958,9 @@ public class NotificationManager {
* Returns whether the calling app's properly formatted notifications can appear in a promoted
* format, which may result in higher ranking, appearances on additional surfaces, and richer
* presentation.
+ *
+ * Apps can request this permission by sending the user to the activity that matches the system
+ * intent action {@link android.provider.Settings#ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS}.
*/
@FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
public boolean canPostPromotedNotifications() {
@@ -3094,4 +3099,19 @@ public class NotificationManager {
}
}
+ /**
+ * Returns the list of {@link Adjustment} keys that the current approved
+ * {@link android.service.notification.NotificationAssistantService} does not support.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public @NonNull Set<String> getUnsupportedAdjustmentTypes() {
+ INotificationManager service = getService();
+ try {
+ return new HashSet<>(service.getUnsupportedAdjustmentTypes());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 84a4eb4acddc..e043a5d0dc67 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -431,16 +431,19 @@ public class ResourcesManager {
}
/**
- * Protected so that tests can override and returns something a fixed value.
+ * public so that tests can access and override
*/
@VisibleForTesting
- protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
+ public @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
final DisplayMetrics dm = new DisplayMetrics();
final DisplayInfo displayInfo = displayManagerGlobal != null
? displayManagerGlobal.getDisplayInfo(displayId) : null;
if (displayInfo != null) {
- displayInfo.getAppMetrics(dm, da);
+ final Configuration dajConfig = da.getConfiguration();
+ displayInfo.getAppMetrics(dm, da.getCompatibilityInfo(),
+ (mResDisplayId == displayId && Configuration.EMPTY.equals(dajConfig))
+ ? mResConfiguration : dajConfig);
} else {
dm.setToDefaults();
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index af242dda9341..e882bb564db9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -275,7 +275,10 @@ public class TaskInfo {
public int parentTaskId;
/**
- * Whether this task is focused.
+ * Whether this task is focused on the display. This means the task receives input events that
+ * target the display.
+ * CAUTION: This can be true for multiple tasks especially when multiple displays are connected
+ * in the system.
* @hide
*/
public boolean isFocused;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 7903f1c0c5c3..2e6f3e1c7f0a 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import android.annotation.CallbackExecutor;
@@ -682,6 +683,53 @@ public class UiModeManager {
}
}
+ private Integer getCurrentModeTypeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getCurrentModeType();
+ }
+ return Configuration.UI_MODE_TYPE_NORMAL;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the current running mode type for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mCurrentModeTypeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getCurrentModeTypeFromServer();
+ }
+ };
+
+ private static final String CURRENT_MODE_TYPE_API = "getCurrentModeType";
+
+ /**
+ * Cache the current running mode type for a user.
+ */
+ private final IpcDataCache<Void, Integer> mCurrentModeTypeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ CURRENT_MODE_TYPE_API, /* cacheName= */ "CurrentModeTypeCache",
+ mCurrentModeTypeQuery);
+
+ /**
+ * Invalidate the current mode type cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_CURRENT_MODE_TYPE_BINDER_CACHE)
+ public static void invalidateCurrentModeTypeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ CURRENT_MODE_TYPE_API);
+ }
+
+
/**
* Return the current running mode type. May be one of
* {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
@@ -693,14 +741,11 @@ public class UiModeManager {
* {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
*/
public int getCurrentModeType() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getCurrentModeType();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableCurrentModeTypeBinderCache()) {
+ return mCurrentModeTypeCache.query(null);
+ } else {
+ return getCurrentModeTypeFromServer();
}
- return Configuration.UI_MODE_TYPE_NORMAL;
}
/**
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index beb93fd079d9..eb0ea1e6ef4f 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,7 +16,10 @@
package android.app.admin;
+import static android.nfc.Flags.FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -100,6 +103,8 @@ public class SecurityLog {
TAG_PACKAGE_UPDATED,
TAG_PACKAGE_UNINSTALLED,
TAG_BACKUP_SERVICE_TOGGLED,
+ TAG_NFC_ENABLED,
+ TAG_NFC_DISABLED,
})
public @interface SecurityLogTag {}
@@ -610,6 +615,18 @@ public class SecurityLog {
*/
public static final int TAG_BACKUP_SERVICE_TOGGLED =
SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+
+ /**
+ * Indicates that NFC service is enabled. There is no extra payload in the log event.
+ */
+ @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+ public static final int TAG_NFC_ENABLED = SecurityLogTags.SECURITY_NFC_ENABLED;
+
+ /**
+ * Indicates that NFC service is disabled. There is no extra payload in the log event.
+ */
+ @FlaggedApi(FLAG_NFC_STATE_CHANGE_SECURITY_LOG_EVENT_ENABLED)
+ public static final int TAG_NFC_DISABLED = SecurityLogTags.SECURITY_NFC_DISABLED;
/**
* Event severity level indicating that the event corresponds to normal workflow.
*/
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 7b3aa7b589b7..8f22c761d535 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -48,4 +48,6 @@ option java_package android.app.admin
210041 security_package_installed (package_name|3),(version_code|1),(user_id|1)
210042 security_package_updated (package_name|3),(version_code|1),(user_id|1)
210043 security_package_uninstalled (package_name|3),(version_code|1),(user_id|1)
-210044 security_backup_service_toggled (package|3),(admin_user|1),(enabled|1) \ No newline at end of file
+210044 security_backup_service_toggled (package|3),(admin_user|1),(enabled|1)
+210045 security_nfc_enabled
+210046 security_nfc_disabled \ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index fe2db49684fd..64dece99c5d1 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -16,6 +16,8 @@
package android.app.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
@@ -166,15 +168,18 @@ public class AppFunctionManagerHelper {
if (runtimeMetadataResults.isEmpty()) {
throw new IllegalArgumentException("App function not found.");
}
- boolean[] enabled =
+ long enabled =
runtimeMetadataResults
.getFirst()
.getGenericDocument()
- .getPropertyBooleanArray(PROPERTY_ENABLED);
- if (enabled != null && enabled.length != 0) {
- return enabled[0];
+ .getPropertyLong(PROPERTY_ENABLED);
+ // If enabled is not equal to APP_FUNCTION_STATE_DEFAULT, it means it IS overridden and
+ // we should return the overridden value.
+ if (enabled != APP_FUNCTION_STATE_DEFAULT) {
+ return enabled == APP_FUNCTION_STATE_ENABLED;
}
- // Runtime metadata not found. Using the default value in the static metadata.
+ // Runtime metadata not found or enabled is equal to APP_FUNCTION_STATE_DEFAULT.
+ // Using the default value in the static metadata.
return joinedStaticRuntimeResults
.getFirst()
.getGenericDocument()
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 8b7f326ee816..08ecced234a9 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -16,11 +16,15 @@
package android.app.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionManager.EnabledState;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
@@ -162,15 +166,13 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
* Returns if the function is set to be enabled or not. If not set, the {@link
* AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT} value would be used.
*/
- @Nullable
- public Boolean getEnabled() {
- // We can't use getPropertyBoolean here. getPropertyBoolean returns false instead of null
- // if the value is missing.
- boolean[] enabled = getPropertyBooleanArray(PROPERTY_ENABLED);
- if (enabled == null || enabled.length == 0) {
- return null;
- }
- return enabled[0];
+ @EnabledState
+ public int getEnabled() {
+ // getPropertyLong returns the first long associated with the given path or default value 0
+ // if there is no such value or the value is of a different type.
+ // APP_FUNCTION_STATE_DEFAULT also equals 0 which means the returned value will be 0 when an
+ // app as either never changed the enabled bit at runtime or has reset it to the default.
+ return (int) getPropertyLong(PROPERTY_ENABLED);
}
/** Returns the qualified id linking to the static metadata of the app function. */
@@ -217,12 +219,14 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
* TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
*/
@NonNull
- public Builder setEnabled(@Nullable Boolean enabled) {
- if (enabled == null) {
- setPropertyBoolean(PROPERTY_ENABLED);
- } else {
- setPropertyBoolean(PROPERTY_ENABLED, enabled);
+ public Builder setEnabled(@EnabledState int enabledState) {
+ if (enabledState != APP_FUNCTION_STATE_DEFAULT
+ && enabledState != APP_FUNCTION_STATE_ENABLED
+ && enabledState != APP_FUNCTION_STATE_DISABLED) {
+ throw new IllegalArgumentException(
+ "Value of EnabledState is unsupported.");
}
+ setPropertyLong(PROPERTY_ENABLED, enabledState);
return this;
}
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 8e417737515e..7a68a656564b 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -35,6 +35,7 @@ import android.os.ICancellationSignal;
import android.os.CancellationSignal;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.util.Log;
import java.util.function.Consumer;
@@ -166,9 +167,13 @@ public abstract class AppFunctionService extends Service {
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
/**
* Called by the system to execute a specific app function.
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 2851e92bd57f..a879b1ba6b5c 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -96,17 +96,14 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
/**
* The operation was cancelled. Use this error code to report that a cancellation is done after
* receiving a cancellation signal.
*/
- public static final int RESULT_CANCELLED = 7;
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -282,7 +279,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED,
RESULT_CANCELLED
})
diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS
index c6827cc93222..6a69e15d00dd 100644
--- a/core/java/android/app/appfunctions/OWNERS
+++ b/core/java/android/app/appfunctions/OWNERS
@@ -4,3 +4,4 @@ toki@google.com
tonymak@google.com
mingweiliao@google.com
anothermark@google.com
+utkarshnigam@google.com
diff --git a/core/java/android/app/appfunctions/TEST_MAPPING b/core/java/android/app/appfunctions/TEST_MAPPING
index 91e82ec0e95b..27517c8a787b 100644
--- a/core/java/android/app/appfunctions/TEST_MAPPING
+++ b/core/java/android/app/appfunctions/TEST_MAPPING
@@ -1,10 +1,7 @@
{
- "postsubmit": [
+ "imports": [
{
- "name": "FrameworksAppFunctionsTests"
- },
- {
- "name": "CtsAppFunctionTestCases"
+ "path": "frameworks/base/services/appfunctions/TEST_MAPPING"
}
]
} \ No newline at end of file
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 9f44a4d5ee01..05b46e02f9ad 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -9,4 +9,15 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ namespace: "systemui"
+ name: "enable_current_mode_type_binder_cache"
+ description: "Enables the use of binder caching for current running mode type."
+ bug: "362572732"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 8916ce27cf68..6fe0a7342216 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -84,11 +84,16 @@ interface IVirtualDevice {
int getDevicePolicy(int policyType);
/**
- * Returns whether the device has a valid microphone.
- */
+ * Returns whether the device has a valid microphone.
+ */
boolean hasCustomAudioInputSupport();
/**
+ * Returns whether this device is allowed to create mirror displays.
+ */
+ boolean canCreateMirrorDisplays();
+
+ /**
* Closes the virtual device and frees all associated resources.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index e9fa3e15fe05..9eb6d5624f75 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -132,8 +132,16 @@ flag {
}
flag {
- namespace: "virtual_devices"
- name: "camera_timestamp_from_surface"
- description: "Pass the surface timestamp to the capture result"
- bug: "351341245"
+ namespace: "virtual_devices"
+ name: "camera_timestamp_from_surface"
+ description: "Pass the surface timestamp to the capture result"
+ bug: "351341245"
+}
+
+flag {
+ namespace: "virtual_devices"
+ name: "enable_limited_vdm_role"
+ description: "New VDM role without trusted displays or input"
+ bug: "370657575"
+ is_exported: true
}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 40ffb0ff5c80..64e9c339f2d6 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -476,6 +476,20 @@ public class FabricatedOverlay {
return entry;
}
+ @NonNull
+ private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+ @NonNull String resourceName, float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit, @Nullable String configuration) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = TypedValue.TYPE_DIMENSION;
+ Preconditions.checkArgumentInRange(dimensionUnit,
+ TypedValue.COMPLEX_UNIT_PX, TypedValue.COMPLEX_UNIT_MM, "dimensionUnit");
+ entry.data = TypedValue.createComplexDimension(dimensionValue, dimensionUnit);
+ entry.configuration = configuration;
+ return entry;
+ }
+
/**
* Sets the resource value in the fabricated overlay for the integer-like types with the
* configuration.
@@ -586,4 +600,25 @@ public class FabricatedOverlay {
mOverlay.entries.add(
generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
}
+
+ /**
+ * Sets the resource value in the fabricated overlay for the dimension type with the
+ * configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dimensionValue the float representing the dimension value
+ * @param dimensionUnit the integer representing the dimension unit
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ @FlaggedApi(android.content.res.Flags.FLAG_DIMENSION_FRRO)
+ public void setResourceValue(
+ @NonNull String resourceName,
+ float dimensionValue,
+ @TypedValue.ComplexDimensionUnit int dimensionUnit,
+ @Nullable String configuration) {
+ ensureValidResourceName(resourceName);
+ mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dimensionValue,
+ dimensionUnit, configuration));
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fb2655c771c4..e985f88f38fc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5039,6 +5039,25 @@ public abstract class PackageManager {
"android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
/**
+ * Used by the system to query a {@link android.content.pm.verify.pkg.VerifierService} provider,
+ * which registers itself via an intent-filter handling this action.
+ *
+ * <p class="note">Only the system can bind to such a verifier service. This is protected by the
+ * {@link android.Manifest.permission#BIND_VERIFICATION_AGENT} permission. The verifier service
+ * app should protect the service by adding this permission in the service declaration in its
+ * manifest.
+ * <p>
+ * A verifier service must be a privileged app and hold the
+ * {@link android.Manifest.permission#VERIFICATION_AGENT} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_VERIFICATION_SERVICE)
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_VERIFY_PACKAGE = "android.content.pm.action.VERIFY_PACKAGE";
+
+ /**
* The names of the requested permissions.
* <p>
* <strong>Type:</strong> String[]
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 160cbdffe5bb..300740e84c60 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -309,3 +309,11 @@ flag {
description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently."
bug: "361776825"
}
+
+flag {
+ name: "verification_service"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the new verification service."
+ bug: "360129103"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index fd1a89692da2..cf65539946a9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -68,6 +68,13 @@ flag {
}
flag {
+ name: "multiuser_widget"
+ namespace: "multiuser"
+ description: "Implement the Multiuser Widget"
+ bug: "365748524"
+}
+
+flag {
name: "enable_biometrics_to_unlock_private_space"
is_exported: true
namespace: "profile_experiences"
@@ -449,7 +456,6 @@ flag {
}
}
-
flag {
name: "caching_development_improvements"
namespace: "multiuser"
@@ -457,3 +463,13 @@ flag {
bug: "364947162"
is_fixed_read_only: true
}
+
+flag {
+ name: "show_custom_unlock_title_inside_private_profile"
+ namespace: "profile_experiences"
+ description: "When private space is unlocked show dynamic title in unlock factor screens based on lock factor set for the profile"
+ bug: "323835257"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
new file mode 100644
index 000000000000..38a7956603ae
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.PersistableBundle;
+
+/**
+ * Oneway interface that allows the verifier to send response or verification results back to
+ * the system.
+ * @hide
+ */
+oneway interface IVerificationSessionCallback {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationIncomplete(int verificationId, int reason);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationComplete(int verificationId, in VerificationStatus status);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
new file mode 100644
index 000000000000..7a9484abd1b1
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+/**
+ * Non-oneway interface that allows the verifier to retrieve information from the system.
+ * @hide
+ */
+interface IVerificationSessionInterface {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ long getTimeoutTime(int verificationId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ long extendTimeRemaining(int verificationId, long additionalMs);
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/IVerifierService.aidl b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
new file mode 100644
index 000000000000..d3071fd67b9d
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/IVerifierService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.content.pm.verify.pkg.VerificationSession;
+
+/**
+ * Oneway interface that allows the system to communicate to the verifier service agent.
+ * @hide
+ */
+oneway interface IVerifierService {
+ void onPackageNameAvailable(in String packageName);
+ void onVerificationCancelled(in String packageName);
+ void onVerificationRequired(in VerificationSession session);
+ void onVerificationRetry(in VerificationSession session);
+ void onVerificationTimeout(int verificationId);
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.aidl b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
new file mode 100644
index 000000000000..ac855850a86c
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+/** @hide */
+parcelable VerificationSession;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
new file mode 100644
index 000000000000..70b4a022f521
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class is used by the system to describe the details about a verification request sent to the
+ * verification agent, aka the verifier. It includes the interfaces for the verifier to communicate
+ * back to the system.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+@SystemApi
+public final class VerificationSession implements Parcelable {
+ /**
+ * The verification cannot be completed because of unknown reasons.
+ */
+ public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0;
+ /**
+ * The verification cannot be completed because the network is unavailable.
+ */
+ public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1;
+ /**
+ * The verification cannot be completed because the network is limited.
+ */
+ public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"VERIFICATION_INCOMPLETE_"}, value = {
+ VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE,
+ VERIFICATION_INCOMPLETE_NETWORK_LIMITED,
+ VERIFICATION_INCOMPLETE_UNKNOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerificationIncompleteReason {
+ }
+
+ private final int mId;
+ private final int mInstallSessionId;
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private final Uri mStagedPackageUri;
+ @NonNull
+ private final SigningInfo mSigningInfo;
+ @NonNull
+ private final List<SharedLibraryInfo> mDeclaredLibraries;
+ @NonNull
+ private final PersistableBundle mExtensionParams;
+ @NonNull
+ private final IVerificationSessionInterface mSession;
+ @NonNull
+ private final IVerificationSessionCallback mCallback;
+
+ /**
+ * Constructor used by the system to describe the details of a verification session.
+ * @hide
+ */
+ public VerificationSession(int id, int installSessionId, @NonNull String packageName,
+ @NonNull Uri stagedPackageUri, @NonNull SigningInfo signingInfo,
+ @NonNull List<SharedLibraryInfo> declaredLibraries,
+ @NonNull PersistableBundle extensionParams,
+ @NonNull IVerificationSessionInterface session,
+ @NonNull IVerificationSessionCallback callback) {
+ mId = id;
+ mInstallSessionId = installSessionId;
+ mPackageName = packageName;
+ mStagedPackageUri = stagedPackageUri;
+ mSigningInfo = signingInfo;
+ mDeclaredLibraries = declaredLibraries;
+ mExtensionParams = extensionParams;
+ mSession = session;
+ mCallback = callback;
+ }
+
+ /**
+ * A unique identifier tied to this specific verification session.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * The package name of the app that is to be verified.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The id of the installation session associated with the verification.
+ */
+ public int getInstallSessionId() {
+ return mInstallSessionId;
+ }
+
+ /**
+ * The Uri of the path where the package's code files are located.
+ */
+ public @NonNull Uri getStagedPackageUri() {
+ return mStagedPackageUri;
+ }
+
+ /**
+ * Signing info of the package to be verified.
+ */
+ public @NonNull SigningInfo getSigningInfo() {
+ return mSigningInfo;
+ }
+
+ /**
+ * Returns a mapping of any shared libraries declared in the manifest
+ * to the {@link SharedLibraryInfo#Type} that is declared. This will be an empty
+ * map if no shared libraries are declared by the package.
+ */
+ @NonNull
+ public List<SharedLibraryInfo> getDeclaredLibraries() {
+ return Collections.unmodifiableList(mDeclaredLibraries);
+ }
+
+ /**
+ * Returns any extension params associated with the verification request.
+ */
+ @NonNull
+ public PersistableBundle getExtensionParams() {
+ return mExtensionParams;
+ }
+
+ /**
+ * Get the value of Clock.elapsedRealtime() at which time this verification
+ * will timeout as incomplete if no other verification response is provided.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public long getTimeoutTime() {
+ try {
+ return mSession.getTimeoutTime(mId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Extend the timeout for this session by the provided additionalMs to
+ * fetch relevant information over the network or wait for the network.
+ * This may be called multiple times. If the request would bypass any max
+ * duration by the system, the method will return a lower value than the
+ * requested amount that indicates how much the time was extended.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public long extendTimeRemaining(long additionalMs) {
+ try {
+ return mSession.extendTimeRemaining(mId, additionalMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report to the system that verification could not be completed along
+ * with an approximate reason to pass on to the installer.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
+ try {
+ mCallback.reportVerificationIncomplete(mId, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report to the system that the verification has completed and the
+ * install process may act on that status to either block in the case
+ * of failure or continue to process the install in the case of success.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationComplete(@NonNull VerificationStatus status) {
+ try {
+ mCallback.reportVerificationComplete(mId, status);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
+ * a result to the extension params provided in the request, which will be passed to the
+ * installer in the installation result.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public void reportVerificationComplete(@NonNull VerificationStatus status,
+ @NonNull PersistableBundle response) {
+ try {
+ mCallback.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private VerificationSession(@NonNull Parcel in) {
+ mId = in.readInt();
+ mInstallSessionId = in.readInt();
+ mPackageName = in.readString8();
+ mStagedPackageUri = Uri.CREATOR.createFromParcel(in);
+ mSigningInfo = SigningInfo.CREATOR.createFromParcel(in);
+ mDeclaredLibraries = in.createTypedArrayList(SharedLibraryInfo.CREATOR);
+ mExtensionParams = in.readPersistableBundle(getClass().getClassLoader());
+ mSession = IVerificationSessionInterface.Stub.asInterface(in.readStrongBinder());
+ mCallback = IVerificationSessionCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeInt(mInstallSessionId);
+ dest.writeString8(mPackageName);
+ Uri.writeToParcel(dest, mStagedPackageUri);
+ mSigningInfo.writeToParcel(dest, flags);
+ dest.writeTypedList(mDeclaredLibraries);
+ dest.writePersistableBundle(mExtensionParams);
+ dest.writeStrongBinder(mSession.asBinder());
+ dest.writeStrongBinder(mCallback.asBinder());
+ }
+
+ @NonNull
+ public static final Creator<VerificationSession> CREATOR = new Creator<>() {
+ @Override
+ public VerificationSession createFromParcel(@NonNull Parcel in) {
+ return new VerificationSession(in);
+ }
+
+ @Override
+ public VerificationSession[] newArray(int size) {
+ return new VerificationSession[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
new file mode 100644
index 000000000000..6a1cb4f2d1d2
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+/** @hide */
+parcelable VerificationStatus;
diff --git a/core/java/android/content/pm/verify/pkg/VerificationStatus.java b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
new file mode 100644
index 000000000000..4d0379d79773
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerificationStatus.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used by the verifier to describe the status of the verification request, whether
+ * it's successful or it has failed along with any relevant details.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public final class VerificationStatus implements Parcelable {
+ /**
+ * The ASL status has not been determined. This happens in situations where the verification
+ * service is not monitoring ASLs, and means the ASL data in the app is not necessarily bad but
+ * can't be trusted.
+ */
+ public static final int VERIFIER_STATUS_ASL_UNDEFINED = 0;
+
+ /**
+ * The app's ASL data is considered to be in a good state.
+ */
+ public static final int VERIFIER_STATUS_ASL_GOOD = 1;
+
+ /**
+ * There is something bad in the app's ASL data; the user should be warned about this when shown
+ * the ASL data and/or appropriate decisions made about the use of this data by the platform.
+ */
+ public static final int VERIFIER_STATUS_ASL_BAD = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"VERIFIER_STATUS_ASL_"}, value = {
+ VERIFIER_STATUS_ASL_UNDEFINED,
+ VERIFIER_STATUS_ASL_GOOD,
+ VERIFIER_STATUS_ASL_BAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerifierStatusAsl {}
+
+ private boolean mIsVerified;
+ private @VerifierStatusAsl int mAslStatus;
+ @NonNull
+ private String mFailuresMessage = "";
+
+ private VerificationStatus() {}
+
+ /**
+ * @return whether the status is set to verified or not.
+ */
+ public boolean isVerified() {
+ return mIsVerified;
+ }
+
+ /**
+ * @return the failure message associated with the failure status.
+ */
+ @NonNull
+ public String getFailureMessage() {
+ return mFailuresMessage;
+ }
+
+ /**
+ * @return the asl status.
+ */
+ public @VerifierStatusAsl int getAslStatus() {
+ return mAslStatus;
+ }
+
+ /**
+ * Builder to construct a {@link VerificationStatus} object.
+ */
+ public static final class Builder {
+ final VerificationStatus mStatus = new VerificationStatus();
+
+ /**
+ * Set in the status whether the verification has succeeded or failed.
+ */
+ @NonNull
+ public Builder setVerified(boolean verified) {
+ mStatus.mIsVerified = verified;
+ return this;
+ }
+
+ /**
+ * Set a developer-facing failure message to include in the verification failure status.
+ */
+ @NonNull
+ public Builder setFailureMessage(@NonNull String failureMessage) {
+ mStatus.mFailuresMessage = failureMessage;
+ return this;
+ }
+
+ /**
+ * Set the ASL status, as defined in {@link VerifierStatusAsl}.
+ */
+ @NonNull
+ public Builder setAslStatus(@VerifierStatusAsl int aslStatus) {
+ mStatus.mAslStatus = aslStatus;
+ return this;
+ }
+
+ /**
+ * Build the status object.
+ */
+ @NonNull
+ public VerificationStatus build() {
+ return mStatus;
+ }
+ }
+
+ private VerificationStatus(Parcel in) {
+ mIsVerified = in.readBoolean();
+ mAslStatus = in.readInt();
+ mFailuresMessage = in.readString8();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mIsVerified);
+ dest.writeInt(mAslStatus);
+ dest.writeString8(mFailuresMessage);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<VerificationStatus> CREATOR = new Creator<>() {
+ @Override
+ public VerificationStatus createFromParcel(@NonNull Parcel in) {
+ return new VerificationStatus(in);
+ }
+
+ @Override
+ public VerificationStatus[] newArray(int size) {
+ return new VerificationStatus[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/verify/pkg/VerifierService.java b/core/java/android/content/pm/verify/pkg/VerifierService.java
new file mode 100644
index 000000000000..ccf211915326
--- /dev/null
+++ b/core/java/android/content/pm/verify/pkg/VerifierService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.pkg;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+/**
+ * A base service implementation for the verifier agent to implement.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+public abstract class VerifierService extends Service {
+ /**
+ * Called when a package name is available for a pending verification,
+ * giving the verifier opportunity to pre-fetch any relevant information
+ * that may be needed should a verification for the package be required.
+ */
+ public abstract void onPackageNameAvailable(@NonNull String packageName);
+
+ /**
+ * Called when a package recently provided via {@link #onPackageNameAvailable}
+ * is no longer expected to be installed. This is a hint that any pre-fetch or
+ * cache created as a result of the previous call may be be cleared.
+ * <p>This method will never be called after {@link #onVerificationRequired} is called for the
+ * same package. Once a verification is officially requested by
+ * {@link #onVerificationRequired}, it cannot be cancelled.
+ * </p>
+ */
+ public abstract void onVerificationCancelled(@NonNull String packageName);
+
+ /**
+ * Called when an application needs to be verified. Details about the
+ * verification and actions that can be taken on it will be encapsulated in
+ * the provided {@link VerificationSession} parameter.
+ */
+ public abstract void onVerificationRequired(@NonNull VerificationSession session);
+
+ /**
+ * Called when a verification needs to be retried. This can be encountered
+ * when a prior verification was marked incomplete and the user has indicated
+ * that they've resolved the issue, or when a timeout is reached, but the
+ * the system is attempting to retry. Details about the
+ * verification and actions that can be taken on it will be encapsulated in
+ * the provided {@link VerificationSession} parameter.
+ */
+ public abstract void onVerificationRetry(@NonNull VerificationSession session);
+
+ /**
+ * Called in the case that an active verification has failed. Any APIs called
+ * on the {@link VerificationSession} instance associated with this {@code verificationId} will
+ * throw an {@link IllegalStateException}.
+ */
+ public abstract void onVerificationTimeout(int verificationId);
+
+ /**
+ * Called when the verifier service is bound to the system.
+ */
+ public @Nullable IBinder onBind(@Nullable Intent intent) {
+ if (intent == null || !PackageManager.ACTION_VERIFY_PACKAGE.equals(intent.getAction())) {
+ return null;
+ }
+ return new IVerifierService.Stub() {
+ @Override
+ public void onPackageNameAvailable(@NonNull String packageName) {
+ VerifierService.this.onPackageNameAvailable(packageName);
+ }
+
+ @Override
+ public void onVerificationCancelled(@NonNull String packageName) {
+ VerifierService.this.onVerificationCancelled(packageName);
+ }
+
+ @Override
+ public void onVerificationRequired(@NonNull VerificationSession session) {
+ VerifierService.this.onVerificationRequired(session);
+ }
+
+ @Override
+ public void onVerificationRetry(@NonNull VerificationSession session) {
+ VerifierService.this.onVerificationRetry(session);
+ }
+
+ @Override
+ public void onVerificationTimeout(int verificationId) {
+ VerifierService.this.onVerificationTimeout(verificationId);
+ }
+ };
+ }
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a5f8199c9a07..0af2f2576137 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -66,3 +66,11 @@ flag {
# This flag is read at boot time.
is_fixed_read_only: true
}
+
+flag {
+ name: "dimension_frro"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for passing a dimension to create an frro"
+ bug: "369672322"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index b11961cc2b21..e3fdd267623e 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -139,6 +139,13 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
public static final int DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS = 8;
/**
+ * Dialog dismissal due to the system being unable to retrieve a WindowManager instance required
+ * to show the dialog.
+ * @hide
+ */
+ public static final int DISMISSED_REASON_ERROR_NO_WM = 9;
+
+ /**
* @hide
*/
@IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED,
@@ -148,7 +155,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
DISMISSED_REASON_ERROR,
DISMISSED_REASON_SERVER_REQUESTED,
DISMISSED_REASON_CREDENTIAL_CONFIRMED,
- DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS})
+ DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS,
+ DISMISSED_REASON_ERROR_NO_WM})
@Retention(RetentionPolicy.SOURCE)
public @interface DismissedReason {}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7185719abdd5..6affd123bfde 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -792,7 +792,6 @@ public final class DisplayManagerGlobal {
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
- setVirtualDisplayState(token, surface != null);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -815,14 +814,6 @@ public final class DisplayManagerGlobal {
}
}
- void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
- try {
- mDm.setVirtualDisplayState(token, isOn);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
void setVirtualDisplayRotation(IVirtualDisplayCallback token, @Surface.Rotation int rotation) {
try {
mDm.setVirtualDisplayRotation(token, rotation);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e5980972d590..36e816af8439 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -455,6 +455,11 @@ public abstract class DisplayManagerInternal {
public abstract void onPresentation(int displayId, boolean isShown);
/**
+ * Called upon the usage of stylus.
+ */
+ public abstract void stylusGestureStarted(long eventTime);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aa1539f69722..b612bca5671e 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -115,9 +115,6 @@ interface IDisplayManager {
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
// No permissions required but must be same Uid as the creator.
- void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
-
- // No permissions required but must be same Uid as the creator.
void setVirtualDisplayRotation(in IVirtualDisplayCallback token, int rotation);
// Get a stable metric for the device's display size. No permissions required.
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 6cc938f5e6f8..32b640583734 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -112,18 +112,6 @@ public final class VirtualDisplay {
}
/**
- * Sets the on/off state for a virtual display.
- *
- * @param isOn Whether the display should be on or off.
- * @hide
- */
- public void setDisplayState(boolean isOn) {
- if (mToken != null) {
- mGlobal.setVirtualDisplayState(mToken, isOn);
- }
- }
-
- /**
* Sets the rotation of the virtual display.
*
* @param rotation the new rotation of the display. May be one of {@link Surface#ROTATION_0},
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 51024ba64bd9..6a39365567e5 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -43,3 +43,10 @@ flag {
description: "Enable usb state update based on udc sysfs"
bug: "339241080"
}
+
+flag {
+ name: "enable_usb_data_signal_staking_internal"
+ namespace: "preload_safety"
+ description: "Enables signal API with staking for internal local service callers"
+ bug: "369382558"
+} \ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index f5469104be7f..1fc91eea3138 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -17,9 +17,11 @@
package android.net.vcn;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS;
import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
@@ -29,6 +31,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.SubscriptionManager;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
@@ -47,6 +51,7 @@ import java.util.Objects;
*
* @hide
*/
+// TODO: Do not store WifiInfo and subscription ID in VcnTransportInfo anymore
public class VcnTransportInfo implements TransportInfo, Parcelable {
@Nullable private final WifiInfo mWifiInfo;
private final int mSubId;
@@ -195,4 +200,42 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
return new VcnTransportInfo[size];
}
};
+
+ /** This class can be used to construct a {@link VcnTransportInfo}. */
+ public static final class Builder {
+ private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
+
+ /** Construct Builder */
+ public Builder() {}
+
+ /**
+ * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
+ *
+ * <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
+ * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
+ *
+ * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
+ * Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
+ * Gateway.
+ * @return this {@link Builder} instance, for chaining
+ */
+ @NonNull
+ public Builder setMinUdpPort4500NatTimeoutSeconds(
+ @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
+ int minUdpPort4500NatTimeoutSeconds) {
+ Preconditions.checkArgument(
+ minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s");
+
+ mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
+ return Builder.this;
+ }
+
+ /** Build a VcnTransportInfo instance */
+ @NonNull
+ public VcnTransportInfo build() {
+ return new VcnTransportInfo(
+ null /* wifiInfo */, INVALID_SUBSCRIPTION_ID, mMinUdpPort4500NatTimeoutSeconds);
+ }
+ }
}
diff --git a/core/java/android/net/vcn/VcnUtils.java b/core/java/android/net/vcn/VcnUtils.java
new file mode 100644
index 000000000000..6dc518097737
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
+
+import java.util.List;
+
+/**
+ * Utility class for VCN callers get information from VCN network
+ *
+ * @hide
+ */
+public class VcnUtils {
+ /** Get the WifiInfo of the VCN's underlying WiFi network */
+ @Nullable
+ public static WifiInfo getWifiInfoFromVcnCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final NetworkCapabilities underlyingCaps =
+ getVcnUnderlyingCaps(connectivityMgr, networkCapabilities);
+
+ if (underlyingCaps == null) {
+ return null;
+ }
+
+ final TransportInfo underlyingTransportInfo = underlyingCaps.getTransportInfo();
+ if (!(underlyingTransportInfo instanceof WifiInfo)) {
+ return null;
+ }
+
+ return (WifiInfo) underlyingTransportInfo;
+ }
+
+ /** Get the subscription ID of the VCN's underlying Cell network */
+ public static int getSubIdFromVcnCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ final NetworkCapabilities underlyingCaps =
+ getVcnUnderlyingCaps(connectivityMgr, networkCapabilities);
+
+ if (underlyingCaps == null) {
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+ final NetworkSpecifier underlyingNetworkSpecifier = underlyingCaps.getNetworkSpecifier();
+ if (!(underlyingNetworkSpecifier instanceof TelephonyNetworkSpecifier)) {
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+ return ((TelephonyNetworkSpecifier) underlyingNetworkSpecifier).getSubscriptionId();
+ }
+
+ @Nullable
+ private static NetworkCapabilities getVcnUnderlyingCaps(
+ @NonNull ConnectivityManager connectivityMgr,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ // Return null if it is not a VCN network
+ if (networkCapabilities.getTransportInfo() == null
+ || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
+ return null;
+ }
+
+ // As of Android 16, VCN has one underlying network, and only one. If there are more
+ // than one networks due to future changes in the VCN mainline code, just take the first
+ // network
+ final List<Network> underlyingNws = networkCapabilities.getUnderlyingNetworks();
+ if (underlyingNws == null) {
+ return null;
+ }
+
+ return connectivityMgr.getNetworkCapabilities(underlyingNws.get(0));
+ }
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b9b5295f89b6..c41e626444c9 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3088,8 +3088,9 @@ public abstract class BatteryStats {
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
"Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
- "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
- "Esw", "Ewa", "Elw", "Eec", "Esc", "Eds"
+ "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa",
+ "Etw", "Esw", "Ewa", "Elw", "Esc",
+ "Eds"
};
@FunctionalInterface
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 80546cd6770f..3b5a99ed089a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -493,6 +493,11 @@ public final class BinderProxy implements IBinder {
public native boolean pingBinder();
/**
+ * Check to see if the process that the binder is in is still alive.
+ *
+ * Note, this only reflects the last known death state, if the object
+ * is linked to death or has made a transactions since the death occurs.
+ *
* @return false if the hosting process is gone
*/
public native boolean isBinderAlive();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a8267d1c9d8c..479aa98f80de 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
@@ -401,33 +402,42 @@ public class Build {
* device. This value never changes while a device is booted, but it may
* increase when the hardware manufacturer provides an OTA update.
* <p>
- * Together with {@link SDK_MINOR_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. <pre>SDK_INT</pre> is
- * increased and <pre>SDK_MINOR_INT</pre> is set to 0 on new Android
- * dessert releases. Between these, Android may also release so called
- * minor releases where <pre>SDK_INT</pre> remains unchanged and
- * <pre>SDK_MINOR_INT</pre> is increased. Minor releases can add new
- * APIs, and have stricter guarantees around backwards compatibility
- * (e.g. no changes gated by <pre>targetSdkVersion</pre>) compared to
- * major releases.
+ * This constant records the major version of Android. Use {@link
+ * SDK_INT_FULL} if you need to consider the minor version of Android
+ * as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
+ * @see #SDK_INT_FULL
*/
public static final int SDK_INT = SystemProperties.getInt(
"ro.build.version.sdk", 0);
/**
- * The minor SDK version of the software currently running on this hardware
- * device. This value never changes while a device is booted, but it may
- * increase when the hardware manufacturer provides an OTA update.
+ * The major and minor SDK version of the software currently running on
+ * this hardware device. This value never changes while a device is
+ * booted, but it may increase when the hardware manufacturer provides
+ * an OTA update.
+ * <p>
+ * <code>SDK_INT</code> is increased on new Android dessert releases,
+ * also called major releases. Between these, Android may also release
+ * minor releases where <code>SDK_INT</code> remains unchanged. Minor
+ * releases can add new APIs, and have stricter guarantees around
+ * backwards compatibility (e.g. no changes gated by
+ * <code>targetSdkVersion</code>) compared to major releases.
* <p>
- * Together with {@link SDK_INT}, this constant defines the
- * <pre>major.minor</pre> version of Android. See {@link SDK_INT} for
- * more information.
+ * <code>SDK_INT_FULL</code> is increased on every release.
+ * <p>
+ * Possible values are defined in {@link
+ * android.os.Build.VERSION_CODES_FULL}.
*/
@FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
- public static final int SDK_MINOR_INT = SystemProperties.getInt(
- "ro.build.version.sdk_minor", 0);
+ public static final int SDK_INT_FULL;
+
+ static {
+ SDK_INT_FULL = VERSION_CODES_FULL.SDK_INT_MULTIPLIER
+ * SystemProperties.getInt("ro.build.version.sdk", 0)
+ + SystemProperties.getInt("ro.build.version.sdk_minor", 0);
+ }
/**
* The SDK version of the software that <em>initially</em> shipped on
@@ -1264,6 +1274,25 @@ public class Build {
}
/**
+ * Enumeration of the currently known SDK major and minor version codes.
+ * The numbers increase for every release, and are guaranteed to be ordered
+ * by the release date of each release. The actual values should be
+ * considered an implementation detail, and the current encoding scheme may
+ * change in the future.
+ *
+ * @see android.os.Build.VERSION#SDK_INT_FULL
+ */
+ @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+ @SuppressLint("AcronymName")
+ public static class VERSION_CODES_FULL {
+ private VERSION_CODES_FULL() {}
+
+ // Use the last 5 digits for the minor version. This allows the
+ // minor version to be set to CUR_DEVELOPMENT.
+ private static final int SDK_INT_MULTIPLIER = 100000;
+ }
+
+ /**
* The vendor API for 2024 Q2
*
* <p>For Android 14-QPR3 and later, the vendor API level is completely decoupled from the SDK
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index beb9a935a6ee..2d3dd1b9383b 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -598,6 +598,11 @@ public class GraphicsEnvironment {
final String abi = chooseAbi(angleInfo);
// Build a path that includes installed native libs and APK
+ // TODO (b/370113081): If the native libraries are not found in this path,
+ // the system libraries will be loaded instead.
+ // This can happen if the ANGLE APK is present,
+ // but accidentally packaged without native libraries.
+ // TBD if this should fail instead of falling back to the system version.
final String paths = angleInfo.nativeLibraryDir
+ File.pathSeparator
+ angleInfo.sourceDir
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 2cb86f753078..769cbdd9886d 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -92,9 +92,9 @@ public class RemoteCallbackList<E extends IInterface> {
/**
* Add a new callback to the list. This callback will remain in the list
* until a corresponding call to {@link #unregister} or its hosting process
- * goes away. If the callback was already registered (determined by
+ * goes away. If the callback was already registered (determined by
* checking to see if the {@link IInterface#asBinder callback.asBinder()}
- * object is already in the list), then it will be left as-is.
+ * object is already in the list), then it will be replaced with the new callback.
* Registrations are not counted; a single call to {@link #unregister}
* will remove a callback after any number calls to register it.
*
@@ -106,7 +106,7 @@ public class RemoteCallbackList<E extends IInterface> {
*
* @param cookie Optional additional data to be associated with this
* callback.
- *
+ *
* @return Returns true if the callback was successfully added to the list.
* Returns false if it was not added, either because {@link #kill} had
* previously been called or the callback's process has gone away.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 461f1e00c415..3ae951170759 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -69,6 +69,8 @@ import android.util.Log;
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -90,6 +92,7 @@ import java.util.Set;
*/
@SystemService(Context.USER_SERVICE)
@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@CachedPropertyDefaults()
public class UserManager {
private static final String TAG = "UserManager";
@@ -108,6 +111,9 @@ public class UserManager {
/** Whether the device is in headless system user mode; null until cached. */
private static Boolean sIsHeadlessSystemUser = null;
+ /** Generated class containing IpcDataCaches. */
+ private final Object mIpcDataCache = new UserManagerCache();
+
/** Maximum length of username.
* @hide
*/
@@ -3766,62 +3772,18 @@ public class UserManager {
return isUserUnlocked(user.getIdentifier());
}
- private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
-
- private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isUserUnlocked(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
- // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
- private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isUserUnlockingOrUnlocked(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
/** @hide */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
public boolean isUserUnlocked(@UserIdInt int userId) {
- return mIsUserUnlockedCache.query(userId);
- }
-
- /** @hide */
- public void disableIsUserUnlockedCache() {
- mIsUserUnlockedCache.disableLocal();
- mIsUserUnlockingOrUnlockedCache.disableLocal();
+ return ((UserManagerCache) mIpcDataCache).isUserUnlocked(mService::isUserUnlocked, userId);
}
/** @hide */
public static final void invalidateIsUserUnlockedCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
+ UserManagerCache.invalidateUserUnlocked();
}
/**
@@ -3852,8 +3814,10 @@ public class UserManager {
/** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @CachedProperty(modsFlagOnOrNone = {}, api = "is_user_unlocked")
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- return mIsUserUnlockingOrUnlockedCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache)
+ .isUserUnlockingOrUnlocked(mService::isUserUnlockingOrUnlocked, userId);
}
/**
@@ -5686,31 +5650,9 @@ public class UserManager {
}
}
- private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
-
- private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
- @Override
- public Boolean recompute(Integer query) {
- try {
- return mService.isQuietModeEnabled(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query < 0;
- }
- };
-
-
/** @hide */
public static final void invalidateQuietModeEnabledCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+ UserManagerCache.invalidateQuietModeEnabled();
}
/**
@@ -5719,13 +5661,15 @@ public class UserManager {
* @param userHandle The user handle of the profile to be queried.
* @return true if the profile is in quiet mode, false otherwise.
*/
+ @CachedProperty(modsFlagOnOrNone = {})
public boolean isQuietModeEnabled(UserHandle userHandle) {
- if (android.multiuser.Flags.cacheQuietModeState()){
+ if (android.multiuser.Flags.cacheQuietModeState()) {
final int userId = userHandle.getIdentifier();
if (userId < 0) {
return false;
}
- return mQuietModeEnabledCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache).isQuietModeEnabled(
+ (UserHandle uh) -> mService.isQuietModeEnabled(uh.getIdentifier()), userHandle);
}
try {
return mService.isQuietModeEnabled(userHandle.getIdentifier());
@@ -6424,41 +6368,21 @@ public class UserManager {
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
- private static final String CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY =
- PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "user_serial_number");
-
- private final PropertyInvalidatedCache<Integer, Integer> mUserSerialNumberCache =
- new PropertyInvalidatedCache<Integer, Integer>(
- 32, CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY) {
- @Override
- public Integer recompute(Integer query) {
- try {
- return mService.getUserSerialNumber(query);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
- @Override
- public boolean bypass(Integer query) {
- return query <= 0;
- }
- };
-
-
/** @hide */
public static final void invalidateUserSerialNumberCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_SERIAL_NUMBER_PROPERTY);
+ UserManagerCache.invalidateUserSerialNumber();
}
/**
* Returns a serial number on this device for a given userId. User handles can be recycled
- * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+ * when deleting and creating users, but serial numbers are not reused until the device is
+ * wiped.
* @param userId
* @return a serial number associated with that user, or -1 if the userId is not valid.
* @hide
*/
@UnsupportedAppUsage
+ @CachedProperty(modsFlagOnOrNone = {})
public int getUserSerialNumber(@UserIdInt int userId) {
// Read only flag should is to fix early access to this API
// cacheUserSerialNumber to be removed after the
@@ -6470,7 +6394,8 @@ public class UserManager {
if (userId == UserHandle.USER_SYSTEM) {
return UserHandle.USER_SERIAL_SYSTEM;
}
- return mUserSerialNumberCache.query(userId);
+ return ((UserManagerCache) mIpcDataCache).getUserSerialNumber(
+ mService::getUserSerialNumber, userId);
}
try {
return mService.getUserSerialNumber(userId);
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 27b1dfbd9b18..d557046280d9 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3027,6 +3027,46 @@ public final class ContactsContract {
*/
@FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
public static final class DefaultAccount {
+ /**
+ * Key in the outgoing Bundle for the default account list.
+ *
+ * @hide
+ */
+ public static final String KEY_ELIGIBLE_DEFAULT_ACCOUNTS =
+ "key_eligible_default_accounts";
+ /**
+ * The method to invoke in order to query eligiblie default accounts.
+ *
+ * @hide
+ */
+ public static final String QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD =
+ "queryEligibleDefaultAccounts";
+ /**
+ * Key in the Bundle for the default account state.
+ *
+ * @hide
+ */
+ public static final String KEY_DEFAULT_ACCOUNT_STATE =
+ "key_default_account_state";
+ /**
+ * The method to invoke in order to set the default account.
+ *
+ * @hide
+ */
+ public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+ "setDefaultAccountForNewContacts";
+ /**
+ * The method to invoke in order to query the default account.
+ *
+ * @hide
+ */
+ public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
+ "queryDefaultAccountForNewContacts";
+
+ private DefaultAccount() {
+
+ }
+
/**
* Represents the state of the default account, and the actual {@link Account} if it's
@@ -3228,6 +3268,94 @@ public final class ContactsContract {
public @interface DefaultAccountState {
}
}
+
+ /**
+ * Get the account that is set as the default account for new contacts, which should be
+ * initially selected when creating a new contact on contact management apps.
+ *
+ * @param resolver the ContentResolver to query.
+ *
+ * @return the default account state for new contacts.
+ * @throws RuntimeException if failed to look up the default account.
+ * @throws IllegalStateException if the default account is in an invalid state.
+ */
+ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+ public static @NonNull DefaultAccountAndState getDefaultAccountForNewContacts(
+ @NonNull ContentResolver resolver) {
+ Bundle response = nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+ QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, null);
+
+ int defaultContactsAccountState = response.getInt(KEY_DEFAULT_ACCOUNT_STATE, -1);
+ if (defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+ String accountName = response.getString(Settings.ACCOUNT_NAME);
+ String accountType = response.getString(Settings.ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ throw new IllegalStateException(
+ "account name and type cannot be null or empty");
+ }
+ return new DefaultAccountAndState(
+ DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD,
+ new Account(accountName, accountType));
+ } else if (defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL
+ || defaultContactsAccountState
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET) {
+ return new DefaultAccountAndState(defaultContactsAccountState, /*cloudAccount=*/
+ null);
+ } else {
+ throw new IllegalStateException("Invalid default account state");
+ }
+ }
+
+ /**
+ * Sets the default account that should be initially selected when creating a new
+ * contact on
+ * contact management apps. Apps can only set one of
+ * The following accounts as the default account:
+ * <ol>
+ * <li> local account
+ * <li> cloud account that are eligible to be set as default account.
+ * </ol>
+ *
+ * @param resolver the ContentResolver to query.
+ * @param defaultAccountAndState the default account and state to be set. To set the
+ * local
+ * account as the
+ * default account, this parameter should be
+ * {@link DefaultAccountAndState#ofLocal()}. To set the a
+ * cloud
+ * account as the default account, this parameter should
+ * be
+ * {@link DefaultAccountAndState#ofCloud(Account)}. To
+ * set
+ * the
+ * default account to a "not set" state, this parameter
+ * should
+ * be {@link DefaultAccountAndState#ofNotSet()}.
+ *
+ * @throws RuntimeException if it fails to set the default account.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
+ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
+ @SystemApi
+ public static void setDefaultAccountForNewContacts(@NonNull ContentResolver resolver,
+ @NonNull DefaultAccountAndState defaultAccountAndState) {
+ Bundle extras = new Bundle();
+
+ extras.putInt(KEY_DEFAULT_ACCOUNT_STATE, defaultAccountAndState.getState());
+ if (defaultAccountAndState.getState()
+ == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+ Account cloudAccount = defaultAccountAndState.getAccount();
+ assert cloudAccount != null;
+ extras.putString(Settings.ACCOUNT_NAME, cloudAccount.name);
+ extras.putString(Settings.ACCOUNT_TYPE, cloudAccount.type);
+ }
+ nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
+ SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, extras);
+ }
}
/**
@@ -9055,30 +9183,6 @@ public final class ContactsContract {
public static final String KEY_DEFAULT_ACCOUNT = "key_default_account";
/**
- * Key in the Bundle for the default account state.
- *
- * @hide
- */
- public static final String KEY_DEFAULT_ACCOUNT_STATE =
- "key_default_contacts_account_state";
-
- /**
- * The method to invoke in order to set the default account.
- *
- * @hide
- */
- public static final String SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
- "setDefaultAccountForNewContacts";
-
- /**
- * The method to invoke in order to query the default account.
- *
- * @hide
- */
- public static final String QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD =
- "queryDefaultAccountForNewContacts";
-
- /**
* Get the account that is set as the default account for new contacts, which should be
* initially selected when creating a new contact on contact management apps.
* If the setting has not been set by any app, it will return null. Once the setting
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 768fc3cb993b..1919f77ff538 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2092,23 +2092,6 @@ public final class Settings {
public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
/**
- * Activity Action: Show Zen Mode visual effects configuration settings.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_BLOCKED_EFFECTS_SETTINGS =
- "android.settings.ZEN_MODE_BLOCKED_EFFECTS_SETTINGS";
-
- /**
- * Activity Action: Show Zen Mode onboarding activity.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ZEN_MODE_ONBOARDING = "android.settings.ZEN_MODE_ONBOARDING";
-
- /**
* Activity Action: Show Zen Mode (aka Do Not Disturb) priority configuration settings.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -2367,6 +2350,21 @@ public final class Settings {
"android.settings.ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW";
/**
+ * Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS
+ = "android.settings.APP_NOTIFICATION_PROMOTION_SETTINGS";
+
+ /**
* Activity Action: Show notification settings for a single app.
* <p>
* Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
@@ -8748,35 +8746,6 @@ public final class Settings {
/** @hide */ public static final int ZEN_DURATION_FOREVER = 0;
/**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- */
- @Readable
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- */
- @Readable
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
- /**
* Whether the in call notification is enabled to play sound during calls. The value is
* boolean (1 or 0).
* @hide
@@ -18072,10 +18041,6 @@ public final class Settings {
MOVED_TO_SECURE = new HashSet<>(8);
MOVED_TO_SECURE.add(Global.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE.add(Global.ZEN_DURATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_UPGRADE_NOTIFICATION);
- MOVED_TO_SECURE.add(Global.SHOW_ZEN_SETTINGS_SUGGESTION);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_UPDATED);
- MOVED_TO_SECURE.add(Global.ZEN_SETTINGS_SUGGESTION_VIEWED);
MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
@@ -18910,40 +18875,6 @@ public final class Settings {
@Readable
public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
-
- /**
- * If nonzero, will show the zen upgrade notification when the user toggles DND on/off.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_UPGRADE_NOTIFICATION}
- */
- @Deprecated
- public static final String SHOW_ZEN_UPGRADE_NOTIFICATION = "show_zen_upgrade_notification";
-
- /**
- * If nonzero, will show the zen update settings suggestion.
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#SHOW_ZEN_SETTINGS_SUGGESTION}
- */
- @Deprecated
- public static final String SHOW_ZEN_SETTINGS_SUGGESTION = "show_zen_settings_suggestion";
-
- /**
- * If nonzero, zen has not been updated to reflect new changes.
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_UPDATED}
- * @hide
- */
- @Deprecated
- public static final String ZEN_SETTINGS_UPDATED = "zen_settings_updated";
-
- /**
- * If nonzero, zen setting suggestion has been viewed by user
- * @hide
- * @deprecated - Use {@link android.provider.Settings.Secure#ZEN_SETTINGS_SUGGESTION_VIEWED}
- */
- @Deprecated
- public static final String ZEN_SETTINGS_SUGGESTION_VIEWED =
- "zen_settings_suggestion_viewed";
-
/**
* Backup and restore agent timeout parameters.
* These parameters are represented by a comma-delimited key-value list.
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 48d7cf768e77..7b7058e83acc 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -393,6 +393,23 @@ public abstract class NotificationAssistantService extends NotificationListenerS
}
}
+ /**
+ * Informs the notification manager about what {@link Adjustment Adjustments} are supported by
+ * this NAS.
+ *
+ * For backwards compatibility, we assume all Adjustment types are supported by the NAS.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public final void setAdjustmentTypeSupportedState(@NonNull @Adjustment.Keys String key,
+ boolean supported) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().setAdjustmentTypeSupportedState(mWrapper, key, supported);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
@Override
public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder,
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 60a7d6b5a3be..c9f464716e72 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -16,6 +16,7 @@
package android.service.notification;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Flags;
@@ -24,6 +25,7 @@ import android.util.ArraySet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
@@ -62,6 +64,7 @@ public class ZenModeDiff {
public static class FieldDiff<T> {
private final T mFrom;
private final T mTo;
+ private final BaseDiff mDetailedDiff;
/**
* Constructor to create a FieldDiff object with the given values.
@@ -71,6 +74,19 @@ public class ZenModeDiff {
public FieldDiff(@Nullable T from, @Nullable T to) {
mFrom = from;
mTo = to;
+ mDetailedDiff = null;
+ }
+
+ /**
+ * Constructor to create a FieldDiff object with the given values, and that has a
+ * detailed BaseDiff.
+ * @param from from (old) value
+ * @param to to (new) value
+ */
+ public FieldDiff(@Nullable T from, @Nullable T to, @Nullable BaseDiff detailedDiff) {
+ mFrom = from;
+ mTo = to;
+ mDetailedDiff = detailedDiff;
}
/**
@@ -92,6 +108,9 @@ public class ZenModeDiff {
*/
@Override
public String toString() {
+ if (mDetailedDiff != null) {
+ return mDetailedDiff.toString();
+ }
return mFrom + "->" + mTo;
}
@@ -99,6 +118,9 @@ public class ZenModeDiff {
* Returns whether this represents an actual diff.
*/
public boolean hasDiff() {
+ if (mDetailedDiff != null) {
+ return mDetailedDiff.hasDiff();
+ }
// note that Objects.equals handles null values gracefully.
return !Objects.equals(mFrom, mTo);
}
@@ -114,7 +136,8 @@ public class ZenModeDiff {
@ExistenceChange private int mExists = NONE;
// Map from field name to diffs for any standalone fields in the object.
- private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+ // LinkedHashMap is specifically chosen here to show insertion order when keys are fetched.
+ private LinkedHashMap<String, FieldDiff> mFields = new LinkedHashMap<>();
// Functions for actually diffing objects and string representations have to be implemented
// by subclasses.
@@ -549,8 +572,16 @@ public class ZenModeDiff {
if (!Objects.equals(from.enabler, to.enabler)) {
addField(FIELD_ENABLER, new FieldDiff<>(from.enabler, to.enabler));
}
- if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
- addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ if (android.app.Flags.modesApi()) {
+ PolicyDiff policyDiff = new PolicyDiff(from.zenPolicy, to.zenPolicy);
+ if (policyDiff.hasDiff()) {
+ addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy,
+ policyDiff));
+ }
+ } else {
+ if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+ addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ }
}
if (from.modified != to.modified) {
addField(FIELD_MODIFIED, new FieldDiff<>(from.modified, to.modified));
@@ -559,9 +590,12 @@ public class ZenModeDiff {
addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
}
if (android.app.Flags.modesApi()) {
- if (!Objects.equals(from.zenDeviceEffects, to.zenDeviceEffects)) {
+ DeviceEffectsDiff deviceEffectsDiff = new DeviceEffectsDiff(from.zenDeviceEffects,
+ to.zenDeviceEffects);
+ if (deviceEffectsDiff.hasDiff()) {
addField(FIELD_ZEN_DEVICE_EFFECTS,
- new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects));
+ new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects,
+ deviceEffectsDiff));
}
if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
addField(FIELD_TRIGGER_DESCRIPTION,
@@ -629,7 +663,7 @@ public class ZenModeDiff {
sb.append(key);
sb.append(":");
- sb.append(diff);
+ sb.append(diff.toString());
}
if (becameActive()) {
@@ -663,4 +697,338 @@ public class ZenModeDiff {
return mActiveDiff != null && !mActiveDiff.to();
}
}
+
+ /**
+ * Diff class representing a change between two
+ * {@link android.service.notification.ZenDeviceEffects}.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static class DeviceEffectsDiff extends BaseDiff {
+ public static final String FIELD_GRAYSCALE = "mGrayscale";
+ public static final String FIELD_SUPPRESS_AMBIENT_DISPLAY = "mSuppressAmbientDisplay";
+ public static final String FIELD_DIM_WALLPAPER = "mDimWallpaper";
+ public static final String FIELD_NIGHT_MODE = "mNightMode";
+ public static final String FIELD_DISABLE_AUTO_BRIGHTNESS = "mDisableAutoBrightness";
+ public static final String FIELD_DISABLE_TAP_TO_WAKE = "mDisableTapToWake";
+ public static final String FIELD_DISABLE_TILT_TO_WAKE = "mDisableTiltToWake";
+ public static final String FIELD_DISABLE_TOUCH = "mDisableTouch";
+ public static final String FIELD_MINIMIZE_RADIO_USAGE = "mMinimizeRadioUsage";
+ public static final String FIELD_MAXIMIZE_DOZE = "mMaximizeDoze";
+ public static final String FIELD_EXTRA_EFFECTS = "mExtraEffects";
+ // NOTE: new field strings must match the variable names in ZenDeviceEffects
+
+ /**
+ * Create a DeviceEffectsDiff representing the difference between two ZenDeviceEffects
+ * objects.
+ * @param from previous ZenDeviceEffects
+ * @param to new ZenDeviceEffects
+ * @return The diff between the two given ZenDeviceEffects
+ */
+ public DeviceEffectsDiff(ZenDeviceEffects from, ZenDeviceEffects to) {
+ super(from, to);
+ // Short-circuit the both-null case
+ if (from == null && to == null) {
+ return;
+ }
+ if (hasExistenceChange()) {
+ // either added or removed; return here. otherwise (they're not both null) there's
+ // field diffs.
+ return;
+ }
+
+ // Compare all fields, knowing there's some diff and that neither is null.
+ if (from.shouldDisplayGrayscale() != to.shouldDisplayGrayscale()) {
+ addField(FIELD_GRAYSCALE, new FieldDiff<>(from.shouldDisplayGrayscale(),
+ to.shouldDisplayGrayscale()));
+ }
+ if (from.shouldSuppressAmbientDisplay() != to.shouldSuppressAmbientDisplay()) {
+ addField(FIELD_SUPPRESS_AMBIENT_DISPLAY,
+ new FieldDiff<>(from.shouldSuppressAmbientDisplay(),
+ to.shouldSuppressAmbientDisplay()));
+ }
+ if (from.shouldDimWallpaper() != to.shouldDimWallpaper()) {
+ addField(FIELD_DIM_WALLPAPER, new FieldDiff<>(from.shouldDimWallpaper(),
+ to.shouldDimWallpaper()));
+ }
+ if (from.shouldUseNightMode() != to.shouldUseNightMode()) {
+ addField(FIELD_NIGHT_MODE, new FieldDiff<>(from.shouldUseNightMode(),
+ to.shouldUseNightMode()));
+ }
+ if (from.shouldDisableAutoBrightness() != to.shouldDisableAutoBrightness()) {
+ addField(FIELD_DISABLE_AUTO_BRIGHTNESS,
+ new FieldDiff<>(from.shouldDisableAutoBrightness(),
+ to.shouldDisableAutoBrightness()));
+ }
+ if (from.shouldDisableTapToWake() != to.shouldDisableTapToWake()) {
+ addField(FIELD_DISABLE_TAP_TO_WAKE, new FieldDiff<>(from.shouldDisableTapToWake(),
+ to.shouldDisableTapToWake()));
+ }
+ if (from.shouldDisableTiltToWake() != to.shouldDisableTiltToWake()) {
+ addField(FIELD_DISABLE_TILT_TO_WAKE,
+ new FieldDiff<>(from.shouldDisableTiltToWake(),
+ to.shouldDisableTiltToWake()));
+ }
+ if (from.shouldDisableTouch() != to.shouldDisableTouch()) {
+ addField(FIELD_DISABLE_TOUCH, new FieldDiff<>(from.shouldDisableTouch(),
+ to.shouldDisableTouch()));
+ }
+ if (from.shouldMinimizeRadioUsage() != to.shouldMinimizeRadioUsage()) {
+ addField(FIELD_MINIMIZE_RADIO_USAGE,
+ new FieldDiff<>(from.shouldMinimizeRadioUsage(),
+ to.shouldMinimizeRadioUsage()));
+ }
+ if (from.shouldMaximizeDoze() != to.shouldMaximizeDoze()) {
+ addField(FIELD_MAXIMIZE_DOZE, new FieldDiff<>(from.shouldMaximizeDoze(),
+ to.shouldMaximizeDoze()));
+ }
+ if (!Objects.equals(from.getExtraEffects(), to.getExtraEffects())) {
+ addField(FIELD_EXTRA_EFFECTS, new FieldDiff<>(from.getExtraEffects(),
+ to.getExtraEffects()));
+ }
+ }
+
+ /**
+ * Returns whether this object represents an actual diff.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange() || hasFieldDiffs();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ZenDeviceEffectsDiff{");
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, we just append that.
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Append all of the individual field diffs
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // The diff should not have null diffs added, but we add this to be defensive.
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+
+ return sb.append("}").toString();
+ }
+ }
+
+ /**
+ * Diff class representing a change between two {@link android.service.notification.ZenPolicy}.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static class PolicyDiff extends BaseDiff {
+ public static final String FIELD_PRIORITY_CATEGORY_REMINDERS =
+ "mPriorityCategories_Reminders";
+ public static final String FIELD_PRIORITY_CATEGORY_EVENTS = "mPriorityCategories_Events";
+ public static final String FIELD_PRIORITY_CATEGORY_MESSAGES =
+ "mPriorityCategories_Messages";
+ public static final String FIELD_PRIORITY_CATEGORY_CALLS = "mPriorityCategories_Calls";
+ public static final String FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS =
+ "mPriorityCategories_RepeatCallers";
+ public static final String FIELD_PRIORITY_CATEGORY_ALARMS = "mPriorityCategories_Alarms";
+ public static final String FIELD_PRIORITY_CATEGORY_MEDIA = "mPriorityCategories_Media";
+ public static final String FIELD_PRIORITY_CATEGORY_SYSTEM = "mPriorityCategories_System";
+ public static final String FIELD_PRIORITY_CATEGORY_CONVERSATIONS =
+ "mPriorityCategories_Conversations";
+
+ public static final String FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT =
+ "mVisualEffects_FullScreenIntent";
+ public static final String FIELD_VISUAL_EFFECT_LIGHTS = "mVisualEffects_Lights";
+ public static final String FIELD_VISUAL_EFFECT_PEEK = "mVisualEffects_Peek";
+ public static final String FIELD_VISUAL_EFFECT_STATUS_BAR = "mVisualEffects_StatusBar";
+ public static final String FIELD_VISUAL_EFFECT_BADGE = "mVisualEffects_Badge";
+ public static final String FIELD_VISUAL_EFFECT_AMBIENT = "mVisualEffects_Ambient";
+ public static final String FIELD_VISUAL_EFFECT_NOTIFICATION_LIST =
+ "mVisualEffects_NotificationList";
+
+ public static final String FIELD_PRIORITY_MESSAGES = "mPriorityMessages";
+ public static final String FIELD_PRIORITY_CALLS = "mPriorityCalls";
+ public static final String FIELD_CONVERSATION_SENDERS = "mConversationSenders";
+ public static final String FIELD_ALLOW_CHANNELS = "mAllowChannels";
+
+ /**
+ * Create a PolicyDiff representing the difference between two ZenPolicy objects.
+ *
+ * @param from previous ZenPolicy
+ * @param to new ZenPolicy
+ * @return The diff between the two given ZenPolicy
+ */
+ public PolicyDiff(ZenPolicy from, ZenPolicy to) {
+ super(from, to);
+ // Short-circuit the both-null case
+ if (from == null && to == null) {
+ return;
+ }
+ if (hasExistenceChange()) {
+ // either added or removed; return here. otherwise (they're not both null) there's
+ // field diffs.
+ return;
+ }
+
+ // Compare all fields, knowing there's some diff and that neither is null.
+ if (from.getPriorityCategoryReminders() != to.getPriorityCategoryReminders()) {
+ addField(FIELD_PRIORITY_CATEGORY_REMINDERS,
+ new FieldDiff<>(from.getPriorityCategoryReminders(),
+ to.getPriorityCategoryReminders()));
+ }
+ if (from.getPriorityCategoryEvents() != to.getPriorityCategoryEvents()) {
+ addField(FIELD_PRIORITY_CATEGORY_EVENTS,
+ new FieldDiff<>(from.getPriorityCategoryEvents(),
+ to.getPriorityCategoryEvents()));
+ }
+ if (from.getPriorityCategoryMessages() != to.getPriorityCategoryMessages()) {
+ addField(FIELD_PRIORITY_CATEGORY_MESSAGES,
+ new FieldDiff<>(from.getPriorityCategoryMessages(),
+ to.getPriorityCategoryMessages()));
+ }
+ if (from.getPriorityCategoryCalls() != to.getPriorityCategoryCalls()) {
+ addField(FIELD_PRIORITY_CATEGORY_CALLS,
+ new FieldDiff<>(from.getPriorityCategoryCalls(),
+ to.getPriorityCategoryCalls()));
+ }
+ if (from.getPriorityCategoryRepeatCallers() != to.getPriorityCategoryRepeatCallers()) {
+ addField(FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS,
+ new FieldDiff<>(from.getPriorityCategoryRepeatCallers(),
+ to.getPriorityCategoryRepeatCallers()));
+ }
+ if (from.getPriorityCategoryAlarms() != to.getPriorityCategoryAlarms()) {
+ addField(FIELD_PRIORITY_CATEGORY_ALARMS,
+ new FieldDiff<>(from.getPriorityCategoryAlarms(),
+ to.getPriorityCategoryAlarms()));
+ }
+ if (from.getPriorityCategoryMedia() != to.getPriorityCategoryMedia()) {
+ addField(FIELD_PRIORITY_CATEGORY_MEDIA,
+ new FieldDiff<>(from.getPriorityCategoryMedia(),
+ to.getPriorityCategoryMedia()));
+ }
+ if (from.getPriorityCategorySystem() != to.getPriorityCategorySystem()) {
+ addField(FIELD_PRIORITY_CATEGORY_SYSTEM,
+ new FieldDiff<>(from.getPriorityCategorySystem(),
+ to.getPriorityCategorySystem()));
+ }
+ if (from.getPriorityCategoryConversations() != to.getPriorityCategoryConversations()) {
+ addField(FIELD_PRIORITY_CATEGORY_CONVERSATIONS,
+ new FieldDiff<>(from.getPriorityCategoryConversations(),
+ to.getPriorityCategoryConversations()));
+ }
+ if (from.getVisualEffectFullScreenIntent() != to.getVisualEffectFullScreenIntent()) {
+ addField(FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT,
+ new FieldDiff<>(from.getVisualEffectFullScreenIntent(),
+ to.getVisualEffectFullScreenIntent()));
+ }
+ if (from.getVisualEffectLights() != to.getVisualEffectLights()) {
+ addField(FIELD_VISUAL_EFFECT_LIGHTS,
+ new FieldDiff<>(from.getVisualEffectLights(), to.getVisualEffectLights()));
+ }
+ if (from.getVisualEffectPeek() != to.getVisualEffectPeek()) {
+ addField(FIELD_VISUAL_EFFECT_PEEK, new FieldDiff<>(from.getVisualEffectPeek(),
+ to.getVisualEffectPeek()));
+ }
+ if (from.getVisualEffectStatusBar() != to.getVisualEffectStatusBar()) {
+ addField(FIELD_VISUAL_EFFECT_STATUS_BAR,
+ new FieldDiff<>(from.getVisualEffectStatusBar(),
+ to.getVisualEffectStatusBar()));
+ }
+ if (from.getVisualEffectBadge() != to.getVisualEffectBadge()) {
+ addField(FIELD_VISUAL_EFFECT_BADGE, new FieldDiff<>(from.getVisualEffectBadge(),
+ to.getVisualEffectBadge()));
+ }
+ if (from.getVisualEffectAmbient() != to.getVisualEffectAmbient()) {
+ addField(FIELD_VISUAL_EFFECT_AMBIENT, new FieldDiff<>(from.getVisualEffectAmbient(),
+ to.getVisualEffectAmbient()));
+ }
+ if (from.getVisualEffectNotificationList() != to.getVisualEffectNotificationList()) {
+ addField(FIELD_VISUAL_EFFECT_NOTIFICATION_LIST,
+ new FieldDiff<>(from.getVisualEffectNotificationList(),
+ to.getVisualEffectNotificationList()));
+ }
+ if (from.getPriorityMessageSenders() != to.getPriorityMessageSenders()) {
+ addField(FIELD_PRIORITY_MESSAGES, new FieldDiff<>(from.getPriorityMessageSenders(),
+ to.getPriorityMessageSenders()));
+ }
+ if (from.getPriorityCallSenders() != to.getPriorityCallSenders()) {
+ addField(FIELD_PRIORITY_CALLS, new FieldDiff<>(from.getPriorityCallSenders(),
+ to.getPriorityCallSenders()));
+ }
+ if (from.getPriorityConversationSenders() != to.getPriorityConversationSenders()) {
+ addField(FIELD_CONVERSATION_SENDERS,
+ new FieldDiff<>(from.getPriorityConversationSenders(),
+ to.getPriorityConversationSenders()));
+ }
+ if (from.getPriorityChannelsAllowed() != to.getPriorityChannelsAllowed()) {
+ addField(FIELD_ALLOW_CHANNELS, new FieldDiff<>(from.getPriorityChannelsAllowed(),
+ to.getPriorityChannelsAllowed()));
+ }
+ }
+
+ /**
+ * Returns whether this object represents an actual diff.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange() || hasFieldDiffs();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ZenPolicyDiff{");
+ // The diff should not have null diffs added, but we add this to be defensive.
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, we just append that.
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Go through all of the individual fields
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen...
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+
+ return sb.append("}").toString();
+ }
+ }
+
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index be0d7b3a72f2..4cff67e24a0f 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -29,6 +29,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.VisibleForTesting;
+
import java.io.ByteArrayOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -207,8 +209,10 @@ public final class ZenPolicy implements Parcelable {
/**
* Total number of priority categories. Keep updated with any updates to PriorityCategory enum.
+ * If this changes, you must update {@link ZenModeDiff.PolicyDiff} to include new categories.
* @hide
*/
+ @VisibleForTesting
public static final int NUM_PRIORITY_CATEGORIES = 9;
/** @hide */
@@ -241,8 +245,10 @@ public final class ZenPolicy implements Parcelable {
/**
* Total number of visual effects. Keep updated with any updates to VisualEffect enum.
+ * If this changes, you must update {@link ZenModeDiff.PolicyDiff} to include new categories.
* @hide
*/
+ @VisibleForTesting
public static final int NUM_VISUAL_EFFECTS = 7;
/** @hide */
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3599332af955..ec5b488ccbbd 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -178,3 +178,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "handwriting_unsupported_show_soft_input_fix"
+ namespace: "text"
+ description: "Don't show soft keyboard on stylus input if text field doesn't support handwriting and getShowSoftInputOnFocus() returns false."
+ bug: "363180475"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/tracing/TEST_MAPPING b/core/java/android/tracing/TEST_MAPPING
new file mode 100644
index 000000000000..b51d19da97a5
--- /dev/null
+++ b/core/java/android/tracing/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ },
+ {
+ "name": "ProtologPerfTests"
+ }
+ ]
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index f1329635f16c..c2179997a92d 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,6 +19,7 @@ package android.view;
import static com.android.text.flags.Flags.handwritingCursorPosition;
import static com.android.text.flags.Flags.handwritingTrackDisabled;
import static com.android.text.flags.Flags.handwritingUnsupportedMessage;
+import static com.android.text.flags.Flags.handwritingUnsupportedShowSoftInputFix;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -241,7 +242,11 @@ public class HandwritingInitiator {
if (!candidateView.hasFocus()) {
requestFocusWithoutReveal(candidateView);
}
- mImm.showSoftInput(candidateView, 0);
+ if (!handwritingUnsupportedShowSoftInputFix()
+ || (candidateView instanceof TextView tv
+ && tv.getShowSoftInputOnFocus())) {
+ mImm.showSoftInput(candidateView, 0);
+ }
mState.mHandled = true;
mState.mShouldInitHandwriting = false;
motionEvent.setAction((motionEvent.getAction()
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 90ceb440ea5e..2748e3288d34 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -152,6 +152,8 @@ public final class SurfaceControl implements Parcelable {
long nativeObject, int priority);
private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
int l, int t, int r, int b);
+ private static native void nativeSetCrop(long transactionObj, long nativeObject,
+ float l, float t, float r, float b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
float cornerRadius);
private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
@@ -3452,6 +3454,29 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+ * only constrained by the size of its parent bounds.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param crop Bounds of the crop to apply.
+ * @return this This transaction for chaining
+ * @hide
+ */
+ public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
+ float bottom, float right) {
+ checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
+ bottom + ", " + right + "}");
+ }
+ nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+ return this;
+ }
+
+ /**
* Sets the corner radius of a {@link SurfaceControl}.
* @param sc SurfaceControl
* @param cornerRadius Corner radius in pixels.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40a75fdcaa06..8fb17c72c462 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -238,7 +238,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
-import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
+import android.view.accessibility.AccessibilityManager.HighContrastTextStateChangeListener;
import android.view.accessibility.AccessibilityNodeIdManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -1800,8 +1800,8 @@ public final class ViewRootImpl implements ViewParent,
}
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
- mAccessibilityManager.addHighTextContrastStateChangeListener(
- mHighContrastTextManager, mHandler);
+ mAccessibilityManager.addHighContrastTextStateChangeListener(
+ mExecutor, mHighContrastTextManager);
DisplayManagerGlobal
.getInstance()
.registerDisplayListener(
@@ -1838,7 +1838,7 @@ public final class ViewRootImpl implements ViewParent,
private void unregisterListeners() {
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
- mAccessibilityManager.removeHighTextContrastStateChangeListener(
+ mAccessibilityManager.removeHighContrastTextStateChangeListener(
mHighContrastTextManager);
DisplayManagerGlobal
.getInstance()
@@ -11907,12 +11907,12 @@ public final class ViewRootImpl implements ViewParent,
}
}
- final class HighContrastTextManager implements HighTextContrastChangeListener {
+ final class HighContrastTextManager implements HighContrastTextStateChangeListener {
HighContrastTextManager() {
- ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighTextContrastEnabled());
+ ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighContrastTextEnabled());
}
@Override
- public void onHighTextContrastStateChanged(boolean enabled) {
+ public void onHighContrastTextStateChanged(boolean enabled) {
ThreadedRenderer.setHighContrastText(enabled);
destroyAndInvalidate();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0582afe6655d..381006c8a21b 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1345,7 +1345,7 @@ public abstract class Window {
}
/**
- * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+ * <p>Sets the desired amount of HDR headroom to be used when rendering as a ratio of
* targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
* {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
*
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 2b7cf427a562..fd57aec4180b 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -253,7 +253,7 @@ public final class AccessibilityManager {
boolean mIsTouchExplorationEnabled;
@UnsupportedAppUsage(trackingBug = 123768939L)
- boolean mIsHighTextContrastEnabled;
+ boolean mIsHighContrastTextEnabled;
boolean mIsAudioDescriptionByDefaultRequested;
@@ -276,8 +276,8 @@ public final class AccessibilityManager {
private final ArrayMap<TouchExplorationStateChangeListener, Handler>
mTouchExplorationStateChangeListeners = new ArrayMap<>();
- private final ArrayMap<HighTextContrastChangeListener, Handler>
- mHighTextContrastStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<HighContrastTextStateChangeListener, Executor>
+ mHighContrastTextStateChangeListeners = new ArrayMap<>();
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
@@ -356,21 +356,20 @@ public final class AccessibilityManager {
}
/**
- * Listener for the system high text contrast state. To listen for changes to
- * the high text contrast state on the device, implement this interface and
+ * Listener for the system high contrast text state. To listen for changes to
+ * the high contrast text state on the device, implement this interface and
* register it with the system by calling
- * {@link #addHighTextContrastStateChangeListener}.
- *
- * @hide
+ * {@link #addHighContrastTextStateChangeListener}.
*/
- public interface HighTextContrastChangeListener {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public interface HighContrastTextStateChangeListener {
/**
- * Called when the high text contrast enabled state changes.
+ * Called when the high contrast text enabled state changes.
*
- * @param enabled Whether high text contrast is enabled.
+ * @param enabled Whether high contrast text is enabled.
*/
- void onHighTextContrastStateChanged(boolean enabled);
+ void onHighContrastTextStateChanged(boolean enabled);
}
/**
@@ -655,24 +654,23 @@ public final class AccessibilityManager {
}
/**
- * Returns if the high text contrast in the system is enabled.
+ * Returns if high contrast text in the system is enabled.
* <p>
* <strong>Note:</strong> You need to query this only if you application is
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
- * @return True if high text contrast is enabled, false otherwise.
+ * @return True if high contrast text is enabled, false otherwise.
*
- * @hide
*/
- @UnsupportedAppUsage
- public boolean isHighTextContrastEnabled() {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public boolean isHighContrastTextEnabled() {
synchronized (mLock) {
IAccessibilityManager service = getServiceLocked();
if (service == null) {
return false;
}
- return mIsHighTextContrastEnabled;
+ return mIsHighContrastTextEnabled;
}
}
@@ -1303,32 +1301,32 @@ public final class AccessibilityManager {
}
/**
- * Registers a {@link HighTextContrastChangeListener} for changes in
- * the global high text contrast state of the system.
+ * Registers a {@link HighContrastTextStateChangeListener} for changes in
+ * the global high contrast text state of the system.
*
- * @param listener The listener.
- *
- * @hide
+ * @param executor a executor to call the listener from
+ * @param listener The listener to be called
*/
- public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void addHighContrastTextStateChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull HighContrastTextStateChangeListener listener
+ ) {
synchronized (mLock) {
- mHighTextContrastStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
+ mHighContrastTextStateChangeListeners.put(listener, executor);
}
}
/**
- * Unregisters a {@link HighTextContrastChangeListener}.
+ * Unregisters a {@link HighContrastTextStateChangeListener}.
*
* @param listener The listener.
- *
- * @hide
*/
- public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
+ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void removeHighContrastTextStateChangeListener(
+ @NonNull HighContrastTextStateChangeListener listener) {
synchronized (mLock) {
- mHighTextContrastStateChangeListeners.remove(listener);
+ mHighContrastTextStateChangeListeners.remove(listener);
}
}
@@ -1505,13 +1503,13 @@ public final class AccessibilityManager {
final boolean wasEnabled = isEnabled();
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+ final boolean wasHighTextContrastEnabled = mIsHighContrastTextEnabled;
final boolean wasAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
// Ensure listeners get current state from isZzzEnabled() calls.
mIsEnabled = enabled;
mIsTouchExplorationEnabled = touchExplorationEnabled;
- mIsHighTextContrastEnabled = highTextContrastEnabled;
+ mIsHighContrastTextEnabled = highTextContrastEnabled;
mIsAudioDescriptionByDefaultRequested = audioDescriptionEnabled;
if (wasEnabled != isEnabled()) {
@@ -1523,7 +1521,7 @@ public final class AccessibilityManager {
}
if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- notifyHighTextContrastStateChanged();
+ notifyHighContrastTextStateChanged();
}
if (wasAudioDescriptionByDefaultRequested
@@ -2397,24 +2395,24 @@ public final class AccessibilityManager {
}
/**
- * Notifies the registered {@link HighTextContrastChangeListener}s.
+ * Notifies the registered {@link HighContrastTextStateChangeListener}s.
*/
- private void notifyHighTextContrastStateChanged() {
+ private void notifyHighContrastTextStateChanged() {
final boolean isHighTextContrastEnabled;
- final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+ final ArrayMap<HighContrastTextStateChangeListener, Executor> listeners;
synchronized (mLock) {
- if (mHighTextContrastStateChangeListeners.isEmpty()) {
+ if (mHighContrastTextStateChangeListeners.isEmpty()) {
return;
}
- isHighTextContrastEnabled = mIsHighTextContrastEnabled;
- listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+ isHighTextContrastEnabled = mIsHighContrastTextEnabled;
+ listeners = new ArrayMap<>(mHighContrastTextStateChangeListeners);
}
final int numListeners = listeners.size();
for (int i = 0; i < numListeners; i++) {
- final HighTextContrastChangeListener listener = listeners.keyAt(i);
- listeners.valueAt(i).post(() ->
- listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+ final HighContrastTextStateChangeListener listener = listeners.keyAt(i);
+ listeners.valueAt(i).execute(() ->
+ listener.onHighContrastTextStateChanged(isHighTextContrastEnabled));
}
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 314bf8985cb4..0dc9263e990d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -28,6 +28,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
@@ -44,6 +45,7 @@ import android.util.ArrayMap;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import com.android.window.flags.Flags;
@@ -267,6 +269,23 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sets whether the IME insets should be excluded by {@link com.android.server.wm.InsetsPolicy}.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi")
+ @NonNull
+ public WindowContainerTransaction setExcludeImeInsets(
+ @NonNull WindowContainerToken container, boolean exclude) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES)
+ .setContainer(container.asBinder())
+ .setExcludeInsetsTypes(exclude ? WindowInsets.Type.ime() : 0)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets whether a container or its children should be hidden. When {@code false}, the existing
* visibility of the container applies, but when {@code true} the container will be forced
* to be hidden.
@@ -1449,6 +1468,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
+ public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1512,6 +1532,8 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mIsTrimmableFromRecents;
+ private @InsetsType int mExcludeInsetsTypes;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1649,6 +1671,7 @@ public final class WindowContainerTransaction implements Parcelable {
mAlwaysOnTop = copy.mAlwaysOnTop;
mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
+ mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
}
protected HierarchyOp(Parcel in) {
@@ -1671,6 +1694,7 @@ public final class WindowContainerTransaction implements Parcelable {
mAlwaysOnTop = in.readBoolean();
mReparentLeafTaskIfRelaunch = in.readBoolean();
mIsTrimmableFromRecents = in.readBoolean();
+ mExcludeInsetsTypes = in.readInt();
}
public int getType() {
@@ -1772,6 +1796,10 @@ public final class WindowContainerTransaction implements Parcelable {
return mIsTrimmableFromRecents;
}
+ public @InsetsType int getExcludeInsetsTypes() {
+ return mExcludeInsetsTypes;
+ }
+
/** Gets a string representation of a hierarchy-op type. */
public static String hopToString(int type) {
switch (type) {
@@ -1795,6 +1823,7 @@ public final class WindowContainerTransaction implements Parcelable {
return "setReparentLeafTaskIfRelaunch";
case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
return "addTaskFragmentOperation";
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
default: return "HOP(" + type + ")";
}
}
@@ -1868,6 +1897,11 @@ public final class WindowContainerTransaction implements Parcelable {
sb.append("fragmentToken= ").append(mContainer)
.append(" operation= ").append(mTaskFragmentOperation);
break;
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES:
+ sb.append("container= ").append(mContainer)
+ .append(" mExcludeInsetsTypes= ")
+ .append(WindowInsets.Type.toString(mExcludeInsetsTypes));
+ break;
case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
sb.append("container= ").append(mContainer)
.append(" isTrimmable= ")
@@ -1903,6 +1937,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeBoolean(mAlwaysOnTop);
dest.writeBoolean(mReparentLeafTaskIfRelaunch);
dest.writeBoolean(mIsTrimmableFromRecents);
+ dest.writeInt(mExcludeInsetsTypes);
}
@Override
@@ -1974,6 +2009,8 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mIsTrimmableFromRecents;
+ private @InsetsType int mExcludeInsetsTypes;
+
Builder(int type) {
mType = type;
}
@@ -2069,6 +2106,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ Builder setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+ mExcludeInsetsTypes = excludeInsetsTypes;
+ return this;
+ }
+
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
hierarchyOp.mContainer = mContainer;
@@ -2093,6 +2135,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mIncludingParents = mIncludingParents;
hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
+ hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
return hierarchyOp;
}
diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java
index 944a106bf441..47af50dac930 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -64,7 +64,8 @@ public enum DesktopModeFlags {
ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
Flags::enableDesktopWindowingTaskbarRunningApps, true),
- ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false);
+ ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index fbc30ed3d8f5..b22aa222d1de 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -162,6 +162,13 @@ flag {
}
flag {
+ name: "enable_a11y_metrics"
+ namespace: "lse_desktop_experience"
+ description: "Whether to enable log collection for a11y actions in desktop windowing mode"
+ bug: "341319597"
+}
+
+flag {
name: "enable_caption_compat_inset_force_consumption"
namespace: "lse_desktop_experience"
description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
@@ -306,3 +313,10 @@ flag {
description: "Allow entering desktop mode by default on freeform displays"
bug: "361419732"
}
+
+flag {
+ name: "enable_desktop_app_launch_alttab_transitions"
+ namespace: "lse_desktop_experience"
+ description: "Enables custom transitions for alt-tab app launches in Desktop Mode."
+ bug: "370735595"
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index fef5e83cecca..4aebde536dcf 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -26,6 +26,7 @@ import android.media.AudioAttributes;
import android.os.RemoteException;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,38 +38,40 @@ public class SystemNotificationChannels {
* @deprecated Legacy system channel, which is no longer used,
*/
@Deprecated public static String VIRTUAL_KEYBOARD = "VIRTUAL_KEYBOARD";
- public static String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
- public static String SECURITY = "SECURITY";
- public static String CAR_MODE = "CAR_MODE";
- public static String ACCOUNT = "ACCOUNT";
- public static String DEVELOPER = "DEVELOPER";
- public static String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
- public static String UPDATES = "UPDATES";
- public static String NETWORK_STATUS = "NETWORK_STATUS";
- public static String NETWORK_ALERTS = "NETWORK_ALERTS";
- public static String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
- public static String VPN = "VPN";
+ public static final String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
+ public static final String SECURITY = "SECURITY";
+ public static final String CAR_MODE = "CAR_MODE";
+ public static final String ACCOUNT = "ACCOUNT";
+ public static final String DEVELOPER = "DEVELOPER";
+ public static final String DEVELOPER_IMPORTANT = "DEVELOPER_IMPORTANT";
+ public static final String UPDATES = "UPDATES";
+ public static final String NETWORK_STATUS = "NETWORK_STATUS";
+ public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
+ public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
+ public static final String VPN = "VPN";
/**
* @deprecated Legacy device admin channel with low importance which is no longer used,
* Use the high importance {@link #DEVICE_ADMIN} channel instead.
*/
- @Deprecated public static String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
- public static String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
- public static String ALERTS = "ALERTS";
- public static String RETAIL_MODE = "RETAIL_MODE";
- public static String USB = "USB";
- public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
- public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
+ @Deprecated public static final String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
+ public static final String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
+ public static final String ALERTS = "ALERTS";
+ public static final String RETAIL_MODE = "RETAIL_MODE";
+ public static final String USB = "USB";
+ public static final String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+ public static final String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
/**
* @deprecated Legacy system changes channel with low importance which is no longer used,
* Use the default importance {@link #SYSTEM_CHANGES} channel instead.
*/
- @Deprecated public static String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
- public static String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
- public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
- public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
- public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
- public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+ @Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
+ public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
+ public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+ public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
+
+ @VisibleForTesting
+ static final String OBSOLETE_DO_NOT_DISTURB = "DO_NOT_DISTURB";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -193,11 +196,6 @@ public class SystemNotificationChannels {
.build());
channelsList.add(systemChanges);
- NotificationChannel dndChanges = new NotificationChannel(DO_NOT_DISTURB,
- context.getString(R.string.notification_channel_do_not_disturb),
- NotificationManager.IMPORTANCE_LOW);
- channelsList.add(dndChanges);
-
final NotificationChannel newFeaturePrompt = new NotificationChannel(
ACCESSIBILITY_MAGNIFICATION,
context.getString(R.string.notification_channel_accessibility_magnification),
@@ -218,6 +216,9 @@ public class SystemNotificationChannels {
channelsList.add(abusiveBackgroundAppsChannel);
nm.createNotificationChannels(channelsList);
+
+ // Delete channels created by previous Android versions that are no longer used.
+ nm.deleteNotificationChannel(OBSOLETE_DO_NOT_DISTURB);
}
private static String getDeviceAdminNotificationChannelName(Context context) {
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 4d0cd27fb037..f3dc896a765e 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -56,6 +56,7 @@ import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
import android.tracing.perfetto.TracingContext;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
@@ -351,6 +352,10 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
}
private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
+ // Verify we don't have id collisions, if we do we want to know as soon as possible and
+ // we might want to manually specify an id for the group with a collision
+ verifyNoCollisionsOrDuplicates(protoLogGroups);
+
final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
mLogGroups.put(protoLogGroup.name(), protoLogGroup);
@@ -369,6 +374,19 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
}
}
+ private void verifyNoCollisionsOrDuplicates(@NonNull IProtoLogGroup[] protoLogGroups) {
+ final var groupId = new ArraySet<Integer>();
+
+ for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+ if (groupId.contains(protoLogGroup.getId())) {
+ throw new RuntimeException(
+ "Group with same id (" + protoLogGroup.getId() + ") registered twice. "
+ + "Potential duplicate or hash id collision.");
+ }
+ groupId.add(protoLogGroup.getId());
+ }
+ }
+
/**
* Responds to a shell command.
*/
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index adf03fe5f775..60213b1830c6 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -22,8 +22,8 @@ import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
/**
* ProtoLog API - exposes static logging methods. Usage of this API is similar
@@ -73,7 +73,7 @@ public class ProtoLog {
if (sProtoLogInstance != null) {
// The ProtoLog instance has already been initialized in this process
final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
- final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+ final var allGroups = new HashSet<>(alreadyRegisteredGroups);
allGroups.addAll(Arrays.stream(groups).toList());
groups = allGroups.toArray(new IProtoLogGroup[0]);
}
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
index 37d57eed8cf4..b51d19da97a5 100644
--- a/core/java/com/android/internal/protolog/TEST_MAPPING
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"postsubmit": [
{
+ "name": "TracingTests"
+ },
+ {
"name": "ProtologPerfTests"
}
]
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9797d9662387..4d2195deebae 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -73,6 +73,12 @@ cc_library_shared_for_libandroid_runtime {
srcs: [
"android_animation_PropertyValuesHolder.cpp",
+ "android_database_CursorWindow.cpp",
+ "android_database_SQLiteCommon.cpp",
+ "android_database_SQLiteConnection.cpp",
+ "android_database_SQLiteGlobal.cpp",
+ "android_database_SQLiteDebug.cpp",
+ "android_database_SQLiteRawStatement.cpp",
"android_os_SystemClock.cpp",
"android_os_SystemProperties.cpp",
"android_os_Trace.cpp",
@@ -157,12 +163,6 @@ cc_library_shared_for_libandroid_runtime {
"android_opengl_GLES31.cpp",
"android_opengl_GLES31Ext.cpp",
"android_opengl_GLES32.cpp",
- "android_database_CursorWindow.cpp",
- "android_database_SQLiteCommon.cpp",
- "android_database_SQLiteConnection.cpp",
- "android_database_SQLiteGlobal.cpp",
- "android_database_SQLiteDebug.cpp",
- "android_database_SQLiteRawStatement.cpp",
"android_graphics_GraphicBuffer.cpp",
"android_graphics_SurfaceTexture.cpp",
"android_view_CompositionSamplingListener.cpp",
@@ -427,6 +427,7 @@ cc_library_shared_for_libandroid_runtime {
"libnativehelper_jvm",
"libpiex",
"libpng",
+ "libsqlite",
"libtiff_directory",
"libui-types",
"libutils",
@@ -442,12 +443,6 @@ cc_library_shared_for_libandroid_runtime {
host_linux: {
srcs: [
"android_content_res_ApkAssets.cpp",
- "android_database_CursorWindow.cpp",
- "android_database_SQLiteCommon.cpp",
- "android_database_SQLiteConnection.cpp",
- "android_database_SQLiteGlobal.cpp",
- "android_database_SQLiteDebug.cpp",
- "android_database_SQLiteRawStatement.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
@@ -463,7 +458,6 @@ cc_library_shared_for_libandroid_runtime {
],
static_libs: [
"libbinderthreadstateutils",
- "libsqlite",
"libgui_window_info_static",
],
shared_libs: [
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index c0e9215267e6..18c314610bb2 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -38,7 +38,9 @@
#define LOG_NDEBUG 1
#include <androidfw/CursorWindow.h>
+#ifdef __linux__
#include "android_os_Parcel.h"
+#endif
#include "android_util_Binder.h"
#include "android_database_SQLiteCommon.h"
@@ -111,6 +113,7 @@ fail:
return 0;
}
+#ifdef __linux__
static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
@@ -128,6 +131,7 @@ static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj
window->getNumRows(), window->getNumColumns(), window);
return reinterpret_cast<jlong>(window);
}
+#endif
static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -142,6 +146,7 @@ static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
return env->NewStringUTF(window->name().c_str());
}
+#ifdef __linux__
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
jobject parcelObj) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -154,6 +159,7 @@ static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
jniThrowRuntimeException(env, msg.c_str());
}
}
+#endif
static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -520,55 +526,35 @@ static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
return true;
}
-static const JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- { "nativeCreate", "(Ljava/lang/String;I)J",
- (void*)nativeCreate },
- { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
- (void*)nativeCreateFromParcel },
- { "nativeDispose", "(J)V",
- (void*)nativeDispose },
- { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
- (void*)nativeWriteToParcel },
-
- { "nativeGetName", "(J)Ljava/lang/String;",
- (void*)nativeGetName },
- { "nativeGetBlob", "(JII)[B",
- (void*)nativeGetBlob },
- { "nativeGetString", "(JII)Ljava/lang/String;",
- (void*)nativeGetString },
- { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
- (void*)nativeCopyStringToBuffer },
- { "nativePutBlob", "(J[BII)Z",
- (void*)nativePutBlob },
- { "nativePutString", "(JLjava/lang/String;II)Z",
- (void*)nativePutString },
-
- // ------- @FastNative below here ----------------------
- { "nativeClear", "(J)V",
- (void*)nativeClear },
- { "nativeGetNumRows", "(J)I",
- (void*)nativeGetNumRows },
- { "nativeSetNumColumns", "(JI)Z",
- (void*)nativeSetNumColumns },
- { "nativeAllocRow", "(J)Z",
- (void*)nativeAllocRow },
- { "nativeFreeLastRow", "(J)V",
- (void*)nativeFreeLastRow },
- { "nativeGetType", "(JII)I",
- (void*)nativeGetType },
- { "nativeGetLong", "(JII)J",
- (void*)nativeGetLong },
- { "nativeGetDouble", "(JII)D",
- (void*)nativeGetDouble },
-
- { "nativePutLong", "(JJII)Z",
- (void*)nativePutLong },
- { "nativePutDouble", "(JDII)Z",
- (void*)nativePutDouble },
- { "nativePutNull", "(JII)Z",
- (void*)nativePutNull },
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Ljava/lang/String;I)J", (void*)nativeCreate},
+ {"nativeDispose", "(J)V", (void*)nativeDispose},
+#ifdef __linux__
+ {"nativeCreateFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeCreateFromParcel},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
+ {"nativeGetName", "(J)Ljava/lang/String;", (void*)nativeGetName},
+ {"nativeGetBlob", "(JII)[B", (void*)nativeGetBlob},
+ {"nativeGetString", "(JII)Ljava/lang/String;", (void*)nativeGetString},
+ {"nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
+ (void*)nativeCopyStringToBuffer},
+ {"nativePutBlob", "(J[BII)Z", (void*)nativePutBlob},
+ {"nativePutString", "(JLjava/lang/String;II)Z", (void*)nativePutString},
+
+ // ------- @FastNative below here ----------------------
+ {"nativeClear", "(J)V", (void*)nativeClear},
+ {"nativeGetNumRows", "(J)I", (void*)nativeGetNumRows},
+ {"nativeSetNumColumns", "(JI)Z", (void*)nativeSetNumColumns},
+ {"nativeAllocRow", "(J)Z", (void*)nativeAllocRow},
+ {"nativeFreeLastRow", "(J)V", (void*)nativeFreeLastRow},
+ {"nativeGetType", "(JII)I", (void*)nativeGetType},
+ {"nativeGetLong", "(JII)J", (void*)nativeGetLong},
+ {"nativeGetDouble", "(JII)D", (void*)nativeGetDouble},
+
+ {"nativePutLong", "(JJII)Z", (void*)nativePutLong},
+ {"nativePutDouble", "(JDII)Z", (void*)nativePutDouble},
+ {"nativePutNull", "(JII)Z", (void*)nativePutNull},
};
int register_android_database_CursorWindow(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 17c89f88b441..71ba2147d984 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -48,6 +48,7 @@
#include <ui/DisplayMode.h>
#include <ui/DisplayedFrameStats.h>
#include <ui/DynamicDisplayInfo.h>
+#include <ui/FloatRect.h>
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
@@ -992,6 +993,15 @@ static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong transactionObj,
transaction->setCrop(ctrl, crop);
}
+static void nativeSetCrop(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jfloat l, jfloat t, jfloat r, jfloat b) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ FloatRect crop(l, t, r, b);
+ transaction->setCrop(ctrl, crop);
+}
+
static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloat cornerRadius) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2347,6 +2357,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetFrameRateSelectionPriority },
{"nativeSetWindowCrop", "(JJIIII)V",
(void*)nativeSetWindowCrop },
+ {"nativeSetCrop", "(JJFFFF)V",
+ (void*)nativeSetCrop },
{"nativeSetCornerRadius", "(JJF)V",
(void*)nativeSetCornerRadius },
{"nativeSetBackgroundBlurRadius", "(JJI)V",
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index 21b5b1308fcf..e3e17eed54d5 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -79,13 +79,14 @@ inline static void setJniMethodFormat(std::string value) {
jniMethodFormat = value;
}
-// Potentially translates the given JNINativeMethods if setJniMethodFormat has been set.
-// Has no effect otherwise
-inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMethods,
- int numMethods) {
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
if (jniMethodFormat.empty()) {
- return gMethods;
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
+
// Make a copy of gMethods with reformatted method names.
JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
@@ -103,13 +104,17 @@ inline const JNINativeMethod* maybeRenameJniMethods(const JNINativeMethod* gMeth
std::strcpy(modifiedNameChars, modifiedName.c_str());
modifiedMethods[i].name = modifiedNameChars;
}
- return modifiedMethods;
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
}
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- const JNINativeMethod* modifiedMethods = maybeRenameJniMethods(gMethods, numMethods);
- int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 19f82998c1a3..88b3e1c1ed9d 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -115,6 +115,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
#ifdef __linux__
{"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
{"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+ {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
+ {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
+#endif
{"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
{"android.database.sqlite.SQLiteConnection",
REG_JNI(register_android_database_SQLiteConnection)},
@@ -122,9 +125,6 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
{"android.database.sqlite.SQLiteRawStatement",
REG_JNI(register_android_database_SQLiteRawStatement)},
-#endif
- {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
- {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
#ifdef __linux__
{"android.os.Binder", REG_JNI(register_android_os_Binder)},
{"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d35c66ed719e..f067b5184bdb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -792,6 +792,7 @@
<protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
<protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+ <protected-broadcast android:name="com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION"/>
<protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
<protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
@@ -8449,6 +8450,29 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.verification_service")
+ Allows app to be the verification agent to verify packages.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.content.pm.verification_service" />
+
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.verification_service")
+ Must be required by a privileged {@link android.content.pm.verify.pkg.VerifierService}
+ to ensure that only the system can bind to it.
+ This permission should not be held by anything other than the system.
+ <p>Not for use by third-party applications. </p>
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_VERIFICATION_AGENT"
+ android:protectionLevel="internal"
+ android:featureFlag="android.content.pm.verification_service" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5c0dca2104af..1a3a30d9c36d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1250,6 +1250,7 @@
a watch, setting this config is no-op.
0 - Nothing
1 - Switch to the recent app
+ 2 - Launch the default fitness app
-->
<integer name="config_doublePressOnStemPrimaryBehavior">0</integer>
@@ -2309,10 +2310,6 @@
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
- <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
- on grouped devices. -->
- <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
-
<!-- Flag indicating current media Output Switcher version. -->
<integer name="config_mediaOutputSwitchDialogVersion">1</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d63421057939..7aca535ad277 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5818,16 +5818,6 @@
<!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
<string name="notification_channel_system_changes">System changes</string>
- <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
- <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
- <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
- <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
- <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
- <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
- <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
<!-- Notification permission informational notification text -->
<!-- Title for notification inviting users to review their notification settings [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 807df1be7fb5..d5298acf0c27 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3937,7 +3937,6 @@
<java-symbol type="string" name="notification_channel_usb" />
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
- <java-symbol type="string" name="notification_channel_do_not_disturb" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="notification_channel_display" />
@@ -4164,11 +4163,6 @@
<!-- For Wear devices -->
<java-symbol type="array" name="config_wearActivityModeRadios" />
- <java-symbol type="string" name="zen_upgrade_notification_title" />
- <java-symbol type="string" name="zen_upgrade_notification_content" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_title" />
- <java-symbol type="string" name="zen_upgrade_notification_visd_content" />
-
<java-symbol type="string" name="review_notification_settings_title" />
<java-symbol type="string" name="review_notification_settings_text" />
<java-symbol type="string" name="review_notification_settings_remind_me_action" />
@@ -5102,8 +5096,6 @@
<java-symbol type="dimen" name="config_wallpaperDimAmount" />
- <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
-
<java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
<!-- List of shared library packages that should be loaded by the classloader after the
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 9821d433500f..56e18e6c443f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -250,7 +250,7 @@ android_ravenwood_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
- "compatibility-device-util-axt",
+ "compatibility-device-util-axt-ravenwood",
"flag-junit",
"platform-test-annotations",
"flag-junit",
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e9b137cd5477..48e26203fab4 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2301,13 +2301,13 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_onProgressStepChange_visiblyDifferent() {
+ public void progressStyle_onProgressPointChange_visiblyDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle()
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isTrue();
@@ -2315,13 +2315,13 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+ public void indeterminateProgressStyle_onProgressPointChange_visiblyNotDifferent() {
final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(10)));
final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
.setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
- .addProgressStep(new Notification.ProgressStyle.Step(12)));
+ .addProgressPoint(new Notification.ProgressStyle.Point(12)));
assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
.isFalse();
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
new file mode 100644
index 000000000000..987f68d4f9e1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningInfo;
+import android.content.pm.VersionedPackage;
+import android.content.pm.verify.pkg.IVerificationSessionCallback;
+import android.content.pm.verify.pkg.IVerificationSessionInterface;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationSessionTest {
+ private static final int TEST_ID = 100;
+ private static final int TEST_INSTALL_SESSION_ID = 33;
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+ private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO1 =
+ new SharedLibraryInfo("sharedLibPath1", TEST_PACKAGE_NAME,
+ Collections.singletonList("path1"), "sharedLib1", 101,
+ SharedLibraryInfo.TYPE_DYNAMIC, new VersionedPackage(TEST_PACKAGE_NAME, 1),
+ null, null, false);
+ private static final SharedLibraryInfo TEST_SHARED_LIBRARY_INFO2 =
+ new SharedLibraryInfo("sharedLibPath2", TEST_PACKAGE_NAME,
+ Collections.singletonList("path2"), "sharedLib2", 102,
+ SharedLibraryInfo.TYPE_DYNAMIC,
+ new VersionedPackage(TEST_PACKAGE_NAME, 2), null, null, false);
+ private static final long TEST_TIMEOUT_TIME = System.currentTimeMillis();
+ private static final long TEST_EXTEND_TIME = 2000L;
+ private static final String TEST_KEY = "test key";
+ private static final String TEST_VALUE = "test value";
+
+ private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
+ private final PersistableBundle mTestExtensionParams = new PersistableBundle();
+ @Mock
+ private IVerificationSessionInterface mTestSessionInterface;
+ @Mock
+ private IVerificationSessionCallback mTestCallback;
+ private VerificationSession mTestSession;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO1);
+ mTestDeclaredLibraries.add(TEST_SHARED_LIBRARY_INFO2);
+ mTestExtensionParams.putString(TEST_KEY, TEST_VALUE);
+ mTestSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+ TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, mTestDeclaredLibraries,
+ mTestExtensionParams, mTestSessionInterface, mTestCallback);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ mTestSession.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ VerificationSession sessionFromParcel =
+ VerificationSession.CREATOR.createFromParcel(parcel);
+ assertThat(sessionFromParcel.getId()).isEqualTo(TEST_ID);
+ assertThat(sessionFromParcel.getInstallSessionId()).isEqualTo(TEST_INSTALL_SESSION_ID);
+ assertThat(sessionFromParcel.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(sessionFromParcel.getStagedPackageUri()).isEqualTo(TEST_PACKAGE_URI);
+ assertThat(sessionFromParcel.getSigningInfo().getSigningDetails())
+ .isEqualTo(TEST_SIGNING_INFO.getSigningDetails());
+ List<SharedLibraryInfo> declaredLibrariesFromParcel =
+ sessionFromParcel.getDeclaredLibraries();
+ assertThat(declaredLibrariesFromParcel).hasSize(2);
+ // SharedLibraryInfo doesn't have a "equals" method, so we have to check it indirectly
+ assertThat(declaredLibrariesFromParcel.getFirst().toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO1.toString());
+ assertThat(declaredLibrariesFromParcel.get(1).toString())
+ .isEqualTo(TEST_SHARED_LIBRARY_INFO2.toString());
+ // We can't directly test with PersistableBundle.equals() because the parceled bundle's
+ // structure is different, but all the key/value pairs should be preserved as before.
+ assertThat(sessionFromParcel.getExtensionParams().getString(TEST_KEY))
+ .isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testInterface() throws Exception {
+ when(mTestSessionInterface.getTimeoutTime(anyInt())).thenAnswer(i -> TEST_TIMEOUT_TIME);
+ when(mTestSessionInterface.extendTimeRemaining(anyInt(), anyLong())).thenAnswer(
+ i -> i.getArguments()[1]);
+
+ assertThat(mTestSession.getTimeoutTime()).isEqualTo(TEST_TIMEOUT_TIME);
+ verify(mTestSessionInterface, times(1)).getTimeoutTime(eq(TEST_ID));
+ assertThat(mTestSession.extendTimeRemaining(TEST_EXTEND_TIME)).isEqualTo(TEST_EXTEND_TIME);
+ verify(mTestSessionInterface, times(1)).extendTimeRemaining(
+ eq(TEST_ID), eq(TEST_EXTEND_TIME));
+ }
+
+ @Test
+ public void testCallback() throws Exception {
+ PersistableBundle response = new PersistableBundle();
+ response.putString("test key", "test value");
+ final VerificationStatus status =
+ new VerificationStatus.Builder().setVerified(true).build();
+ mTestSession.reportVerificationComplete(status);
+ verify(mTestCallback, times(1)).reportVerificationComplete(
+ eq(TEST_ID), eq(status));
+ mTestSession.reportVerificationComplete(status, response);
+ verify(mTestCallback, times(1))
+ .reportVerificationCompleteWithExtensionResponse(
+ eq(TEST_ID), eq(status), eq(response));
+
+ final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
+ mTestSession.reportVerificationIncomplete(reason);
+ verify(mTestCallback, times(1)).reportVerificationIncomplete(
+ eq(TEST_ID), eq(reason));
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
new file mode 100644
index 000000000000..67d407a72925
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationStatusTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.verify.pkg.VerificationStatus;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerificationStatusTest {
+ private static final boolean TEST_VERIFIED = true;
+ private static final int TEST_ASL_STATUS = VerificationStatus.VERIFIER_STATUS_ASL_GOOD;
+ private static final String TEST_FAILURE_MESSAGE = "test test";
+ private static final String TEST_KEY = "test key";
+ private static final String TEST_VALUE = "test value";
+ private final PersistableBundle mTestExtras = new PersistableBundle();
+ private VerificationStatus mStatus;
+
+ @Before
+ public void setUpWithBuilder() {
+ mTestExtras.putString(TEST_KEY, TEST_VALUE);
+ mStatus = new VerificationStatus.Builder()
+ .setAslStatus(TEST_ASL_STATUS)
+ .setFailureMessage(TEST_FAILURE_MESSAGE)
+ .setVerified(TEST_VERIFIED)
+ .build();
+ }
+
+ @Test
+ public void testGetters() {
+ assertThat(mStatus.isVerified()).isEqualTo(TEST_VERIFIED);
+ assertThat(mStatus.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+ assertThat(mStatus.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ mStatus.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ VerificationStatus statusFromParcel = VerificationStatus.CREATOR.createFromParcel(parcel);
+ assertThat(statusFromParcel.isVerified()).isEqualTo(TEST_VERIFIED);
+ assertThat(statusFromParcel.getAslStatus()).isEqualTo(TEST_ASL_STATUS);
+ assertThat(statusFromParcel.getFailureMessage()).isEqualTo(TEST_FAILURE_MESSAGE);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
new file mode 100644
index 000000000000..7f73a1eb4b48
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningInfo;
+import android.content.pm.verify.pkg.IVerifierService;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerifierService;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VerifierServiceTest {
+ private static final int TEST_ID = 100;
+ private static final int TEST_INSTALL_SESSION_ID = 33;
+ private static final String TEST_PACKAGE_NAME = "com.foo";
+ private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
+ private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private VerifierService mService;
+ private VerificationSession mSession;
+
+ @Before
+ public void setUp() {
+ mService = Mockito.mock(VerifierService.class, Answers.CALLS_REAL_METHODS);
+ mSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
+ TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO,
+ new ArrayList<>(),
+ new PersistableBundle(), null, null);
+ }
+
+ @Test
+ public void testBind() throws Exception {
+ Intent intent = Mockito.mock(Intent.class);
+ when(intent.getAction()).thenReturn(PackageManager.ACTION_VERIFY_PACKAGE);
+ IVerifierService binder =
+ (IVerifierService) mService.onBind(intent);
+ assertThat(binder).isNotNull();
+ binder.onPackageNameAvailable(TEST_PACKAGE_NAME);
+ verify(mService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
+ binder.onVerificationCancelled(TEST_PACKAGE_NAME);
+ verify(mService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
+ binder.onVerificationRequired(mSession);
+ verify(mService).onVerificationRequired(eq(mSession));
+ binder.onVerificationRetry(mSession);
+ verify(mService).onVerificationRetry(eq(mSession));
+ binder.onVerificationTimeout(TEST_ID);
+ verify(mService).onVerificationTimeout(eq(TEST_ID));
+ }
+
+ @Test
+ public void testBindFailsWithWrongIntent() {
+ Intent intent = Mockito.mock(Intent.class);
+ when(intent.getAction()).thenReturn(Intent.ACTION_SEND);
+ assertThat(mService.onBind(intent)).isNull();
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 3eefe044de90..b16c237252f6 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -119,7 +119,7 @@ public class ResourcesManagerTest {
}
@Override
- protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
+ public DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
return mDisplayMetricsMap.get(displayId);
}
};
@@ -470,6 +470,48 @@ public class ResourcesManagerTest {
@Test
@SmallTest
+ public void testResourceConfigurationAppliedWhenOverrideDoesNotExist() {
+ final int width = 240;
+ final int height = 360;
+ final float densityDpi = mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).densityDpi;
+ final int widthDp = (int) (width / densityDpi + 0.5f);
+ final int heightDp = (int) (height / densityDpi + 0.5f);
+
+ final int overrideWidth = 480;
+ final int overrideHeight = 720;
+ final int overrideWidthDp = (int) (overrideWidth / densityDpi + 0.5f);
+ final int overrideHeightDp = (int) (height / densityDpi + 0.5f);
+
+ // The method to be tested is overridden for other tests to provide a setup environment.
+ // Create a new one for this test only.
+ final ResourcesManager resourcesManager = new ResourcesManager();
+
+ Configuration newConfig = new Configuration();
+ newConfig.windowConfiguration.setAppBounds(0, 0, width, height);
+ newConfig.screenWidthDp = widthDp;
+ newConfig.screenHeightDp = heightDp;
+ resourcesManager.applyConfigurationToResources(newConfig, null);
+
+ assertEquals(width, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).widthPixels);
+ assertEquals(height, resourcesManager.getDisplayMetrics(Display.DEFAULT_DISPLAY,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS).heightPixels);
+
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.windowConfiguration.setAppBounds(0, 0, overrideWidth, overrideHeight);
+ overrideConfig.screenWidthDp = overrideWidthDp;
+ overrideConfig.screenHeightDp = overrideHeightDp;
+
+ final DisplayAdjustments daj = new DisplayAdjustments(overrideConfig);
+
+ assertEquals(overrideWidth, resourcesManager.getDisplayMetrics(
+ Display.DEFAULT_DISPLAY, daj).widthPixels);
+ assertEquals(overrideHeight, resourcesManager.getDisplayMetrics(
+ Display.DEFAULT_DISPLAY, daj).heightPixels);
+ }
+
+ @Test
+ @SmallTest
@RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
@DisabledOnRavenwood(blockedBy = PackageManager.class)
public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
diff --git a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
new file mode 100644
index 000000000000..0bf406c970f2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.notification;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
+import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.CAR_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER;
+import static com.android.internal.notification.SystemNotificationChannels.DEVELOPER_IMPORTANT;
+import static com.android.internal.notification.SystemNotificationChannels.DEVICE_ADMIN;
+import static com.android.internal.notification.SystemNotificationChannels.FOREGROUND_SERVICE;
+import static com.android.internal.notification.SystemNotificationChannels.HEAVY_WEIGHT_APP;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_ALERTS;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_AVAILABLE;
+import static com.android.internal.notification.SystemNotificationChannels.NETWORK_STATUS;
+import static com.android.internal.notification.SystemNotificationChannels.OBSOLETE_DO_NOT_DISTURB;
+import static com.android.internal.notification.SystemNotificationChannels.PHYSICAL_KEYBOARD;
+import static com.android.internal.notification.SystemNotificationChannels.RETAIL_MODE;
+import static com.android.internal.notification.SystemNotificationChannels.SECURITY;
+import static com.android.internal.notification.SystemNotificationChannels.SYSTEM_CHANGES;
+import static com.android.internal.notification.SystemNotificationChannels.UPDATES;
+import static com.android.internal.notification.SystemNotificationChannels.USB;
+import static com.android.internal.notification.SystemNotificationChannels.VPN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemNotificationChannelsTest {
+
+ @Rule public TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Mock private NotificationManager mNm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(NotificationManager.class, mNm);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void createAll_createsExpectedChannels() {
+ ArgumentCaptor<List<NotificationChannel>> createdChannelsCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm).createNotificationChannels(createdChannelsCaptor.capture());
+ List<NotificationChannel> createdChannels = createdChannelsCaptor.getValue();
+ assertThat(createdChannels.stream().map(NotificationChannel::getId).toList())
+ .containsExactly(PHYSICAL_KEYBOARD, SECURITY, CAR_MODE, ACCOUNT, DEVELOPER,
+ DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
+ NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
+ FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
+ ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
+ ABUSIVE_BACKGROUND_APPS);
+ }
+
+ @Test
+ public void createAll_deletesObsoleteChannels() {
+ ArgumentCaptor<String> deletedChannelCaptor = ArgumentCaptor.forClass(String.class);
+
+ SystemNotificationChannels.createAll(mContext);
+
+ verify(mNm, atLeastOnce()).deleteNotificationChannel(deletedChannelCaptor.capture());
+ List<String> deletedChannels = deletedChannelCaptor.getAllValues();
+ assertThat(deletedChannels).containsExactly(OBSOLETE_DO_NOT_DISTURB);
+ }
+}
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index cdc8a9e06d0b..7cf49ab5c376 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -61,7 +61,7 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"frameworks-base-testutils",
- "servicestests-utils",
+ "servicestests-utils-ravenwood",
],
srcs: [
"src/android/util/IRemoteMemoryIntArray.aidl",
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 9887c272e7f8..af26bd0a3404 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -130,10 +130,6 @@ public final class RequiresPermissionChecker extends BugChecker
.onDescendantOf("android.content.Context")
.withNameMatching(
Pattern.compile("^send(Ordered|Sticky)?Broadcast.*AsUser.*$")));
- private static final Matcher<ExpressionTree> SEND_PENDING_INTENT = methodInvocation(
- instanceMethod()
- .onDescendantOf("android.app.PendingIntent")
- .named("send"));
private static final Matcher<ExpressionTree> INTENT_SET_ACTION = methodInvocation(
instanceMethod().onDescendantOf("android.content.Intent").named("setAction"));
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 0b3e5456d81c..28c2ca36fd2e 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -155,7 +155,7 @@ public class Canvas extends BaseCanvas {
/**
* Indicates whether this Canvas is drawing high contrast text.
*
- * @see android.view.accessibility.AccessibilityManager#isHighTextContrastEnabled()
+ * @see android.view.accessibility.AccessibilityManager#isHighContrastTextEnabled()
* @return True if high contrast text is enabled, false otherwise.
*
* @hide
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b866382e4061..68d8ebbbdabf 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -41,6 +41,7 @@ import android.text.SpannedString;
import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.text.flags.Flags;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -2000,6 +2001,14 @@ public class Paint {
}
/**
+ * A change ID for new font variation settings management.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = 36)
+ public static final long NEW_FONT_VARIATION_MANAGEMENT = 361260253L;
+
+ /**
* Sets TrueType or OpenType font variation settings. The settings string is constructed from
* multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
* and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
@@ -2028,12 +2037,16 @@ public class Paint {
* </li>
* </ul>
*
+ * <p>Note: If the application that targets API 35 or before, this function mutates the
+ * underlying typeface instance.
+ *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
- * @return true if the given settings is effective to at least one font file underlying this
- * typeface. This function also returns true for empty settings string. Otherwise
- * returns false
+ * @return If the application that targets API 36 or later and is running on devices API 36 or
+ * later, this function always returns true. Otherwise, this function returns true if
+ * the given settings is effective to at least one font file underlying this typeface.
+ * This function also returns true for empty settings string. Otherwise returns false.
*
* @throws IllegalArgumentException If given string is not a valid font variation settings
* format
@@ -2042,6 +2055,26 @@ public class Paint {
* @see FontVariationAxis
*/
public boolean setFontVariationSettings(String fontVariationSettings) {
+ final boolean useFontVariationStore = Flags.typefaceRedesign()
+ && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
+ if (useFontVariationStore) {
+ FontVariationAxis[] axes =
+ FontVariationAxis.fromFontVariationSettings(fontVariationSettings);
+ if (axes == null) {
+ nSetFontVariationOverride(mNativePaint, 0);
+ mFontVariationSettings = null;
+ return true;
+ }
+
+ long builderPtr = nCreateFontVariationBuilder(axes.length);
+ for (int i = 0; i < axes.length; ++i) {
+ nAddFontVariationToBuilder(builderPtr, axes[i].getOpenTypeTagValue(),
+ axes[i].getStyleValue());
+ }
+ nSetFontVariationOverride(mNativePaint, builderPtr);
+ mFontVariationSettings = fontVariationSettings;
+ return true;
+ }
final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
if (settings == mFontVariationSettings
|| (settings != null && settings.equals(mFontVariationSettings))) {
@@ -3829,7 +3862,12 @@ public class Paint {
private static native void nSetTextSize(long paintPtr, float textSize);
@CriticalNative
private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
-
+ @CriticalNative
+ private static native long nCreateFontVariationBuilder(int size);
+ @CriticalNative
+ private static native void nAddFontVariationToBuilder(long builderPtr, int tag, float value);
+ @CriticalNative
+ private static native void nSetFontVariationOverride(long paintPtr, long builderPtr);
// Following Native methods are kept for old Robolectric JNI signature used by
// SystemUIGoogleRoboRNGTests
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f8574294a3a2..e493ed1110c8 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -197,6 +197,7 @@ java_library {
android_library {
name: "WindowManager-Shell",
srcs: [
+ "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
@@ -220,6 +221,7 @@ android_library {
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
+ "PlatformAnimationLib",
"WindowManager-Shell-proto",
"WindowManager-Shell-lite-proto",
"WindowManager-Shell-shared",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
new file mode 100644
index 000000000000..db960d15c526
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags
@@ -0,0 +1,11 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.wm.shell
+
+# Do not change these names without updating the checkin_events setting in
+# google3/googledata/wireless/android/provisioning/gservices.config !!
+#
+
+38500 wm_shell_enter_desktop_mode (EnterReason|1|5),(SessionId|1|5)
+38501 wm_shell_exit_desktop_mode (ExitReason|1|5),(SessionId|1|5)
+38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1)
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 af4a0c55f28d..03b7c8b7fe1b 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
@@ -2001,6 +2001,10 @@ public class BubbleController implements ConfigurationChangeListener,
// in bubble bar mode, let the request to show the expanded view come from launcher.
// only collapse here if we're collapsing.
if (mLayerView != null && !isExpanded) {
+ if (mBubblePositioner.isImeVisible()) {
+ // If we're collapsing, hide the IME
+ hideCurrentInputMethod();
+ }
mLayerView.collapse();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ec235a5d84ab..2a9001728cc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -353,6 +353,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (isDragging != mIsDragging) {
mIsDragging = isDragging;
updateSamplingState();
+
+ if (isDragging && mPositioner.isImeVisible()) {
+ // Hide the IME when dragging begins
+ mManager.hideCurrentInputMethod();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 0047ec503504..38087c066918 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -157,6 +157,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
+ private void dispatchImeRequested(int displayId, boolean isRequested) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImeRequested(displayId, isRequested);
+ }
+ }
+ }
+
@ImePositionProcessor.ImeAnimationFlags
private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean show, boolean isFloating, SurfaceControl.Transaction t) {
@@ -398,6 +406,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
mImeRequestedVisible = visible;
+ dispatchImeRequested(mDisplayId, mImeRequestedVisible);
+
// In the case that the IME becomes visible, but we have the control with leash
// already (e.g., when focussing an editText in activity B, while and editText in
// activity A is focussed), we will not get a call of #insetsControlChanged, and
@@ -446,6 +456,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (imeSource == null || mImeSourceControl == null) {
return;
}
+ // TODO(b/353463205): For hide: this still has the statsToken from the previous show
+ // request
final var statsToken = mImeSourceControl.getImeStatsToken();
startAnimation(show, forceRestart, statsToken);
@@ -706,6 +718,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
/**
+ * Called when the IME was requested by an app
+ *
+ * @param isRequested {@code true} if the IME was requested to be visible
+ */
+ default void onImeRequested(int displayId, boolean isRequested) {
+ }
+
+ /**
* Called when the IME position is starting to animate.
*
* @param hiddenTop The y position of the top of the IME surface when it is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 4b55fd0f5527..83ffaf473999 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1106,6 +1106,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
default void onDoubleTappedDivider() {
}
+ /**
+ * Sets the excludedInsetsTypes for the IME in the root WindowContainer.
+ */
+ void setExcludeImeInsets(boolean exclude);
+
/** Returns split position of the token. */
@SplitPosition
int getSplitItemPosition(WindowContainerToken token);
@@ -1305,6 +1310,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
@Override
+ public void onImeRequested(int displayId, boolean isRequested) {
+ if (displayId != mDisplayId) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s",
+ isRequested);
+ mSplitLayoutHandler.setExcludeImeInsets(true);
+ }
+
+ @Override
public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
if (displayId != mDisplayId || !mInitialized) {
@@ -1356,6 +1369,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
"onImeStartPositioning");
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (mImeShown) {
+ mSplitLayoutHandler.setExcludeImeInsets(false);
+ }
+ }
+
return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@@ -1374,6 +1393,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
"Split IME animation ending, canceled=%b", cancel);
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (!mImeShown) {
+ // The IME hide animation is started immediately and at that point, the IME
+ // insets are not yet set to hidden. Therefore only resetting the
+ // excludedTypes at the end of the animation. Note: InsetsPolicy will only
+ // set the IME height to zero, when it is visible. When it becomes invisible,
+ // we dispatch the insets (the height there is zero as well)
+ mSplitLayoutHandler.setExcludeImeInsets(false);
+ }
+ }
}
@Override
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 4227a6e2903f..2a5a519272c7 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
@@ -87,7 +87,7 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -267,7 +267,7 @@ public abstract class WMShellBaseModule {
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -281,7 +281,7 @@ public abstract class WMShellBaseModule {
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
final IntPredicate inDesktopModePredicate =
- desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+ desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
modeTaskRepository.getVisibleTaskCount(displayId) > 0)
.orElseGet(() -> displayId -> false);
return Optional.of(
@@ -707,14 +707,14 @@ public abstract class WMShellBaseModule {
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellController,
shellCommandHandler, taskStackListener, activityTaskManager,
- desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor));
+ desktopRepository, taskStackTransitionObserver, mainExecutor));
}
@BindsOptionalOf
@@ -1003,16 +1003,16 @@ public abstract class WMShellBaseModule {
@BindsOptionalOf
@DynamicOverride
- abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
+ abstract DesktopRepository optionalDesktopRepository();
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context,
- @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ static Optional<DesktopRepository> provideDesktopRepository(Context context,
+ @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy) -> {
+ return desktopRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
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 41a2ee67e8ae..0dca97c8e73c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.dagger;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import android.annotation.Nullable;
@@ -61,12 +62,14 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
@@ -87,6 +90,8 @@ import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarterInitializer;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
@@ -240,7 +245,7 @@ public abstract class WMShellModule {
IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
- @DynamicOverride DesktopModeTaskRepository desktopRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -332,9 +337,13 @@ public abstract class WMShellModule {
static FreeformComponents provideFreeformComponents(
FreeformTaskListener taskListener,
FreeformTaskTransitionHandler transitionHandler,
- FreeformTaskTransitionObserver transitionObserver) {
+ FreeformTaskTransitionObserver transitionObserver,
+ FreeformTaskTransitionStarterInitializer transitionStarterInitializer) {
return new FreeformComponents(
- taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver));
+ taskListener,
+ Optional.of(transitionHandler),
+ Optional.of(transitionObserver),
+ Optional.of(transitionStarterInitializer));
}
@WMSingleton
@@ -343,7 +352,7 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
@@ -352,33 +361,21 @@ public abstract class WMShellModule {
? shellInit
: null;
return new FreeformTaskListener(context, init, shellTaskOrganizer,
- desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel);
+ desktopRepository, launchAdjacentController, windowDecorViewModel);
}
@WMSingleton
@Provides
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
- ShellInit shellInit,
Transitions transitions,
- Context context,
- WindowDecorViewModel windowDecorViewModel,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor,
- @ShellMainThread Handler handler) {
+ @ShellAnimationThread ShellExecutor animExecutor) {
return new FreeformTaskTransitionHandler(
- shellInit,
transitions,
- context,
- windowDecorViewModel,
displayController,
mainExecutor,
- animExecutor,
- desktopModeTaskRepository,
- interactionJankMonitor,
- handler);
+ animExecutor);
}
@WMSingleton
@@ -392,6 +389,23 @@ public abstract class WMShellModule {
context, shellInit, transitions, windowDecorViewModel);
}
+ @WMSingleton
+ @Provides
+ static FreeformTaskTransitionStarterInitializer provideFreeformTaskTransitionStarterInitializer(
+ ShellInit shellInit,
+ WindowDecorViewModel windowDecorViewModel,
+ FreeformTaskTransitionHandler freeformTaskTransitionHandler,
+ Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler) {
+ FreeformTaskTransitionStarter transitionStarter;
+ if (desktopMixedTransitionHandler.isPresent()) {
+ transitionStarter = desktopMixedTransitionHandler.get();
+ } else {
+ transitionStarter = freeformTaskTransitionHandler;
+ }
+ return new FreeformTaskTransitionStarterInitializer(shellInit, windowDecorViewModel,
+ transitionStarter);
+ }
+
//
// One handed mode
//
@@ -606,7 +620,7 @@ public abstract class WMShellModule {
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
@@ -622,7 +636,7 @@ public abstract class WMShellModule {
returnToDragStartAnimator, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ dragToDesktopTransitionHandler, desktopRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
@@ -633,7 +647,7 @@ public abstract class WMShellModule {
static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
Context context,
Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @DynamicOverride DesktopRepository desktopRepository,
ShellTaskOrganizer shellTaskOrganizer,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
@@ -646,7 +660,7 @@ public abstract class WMShellModule {
return Optional.of(
new DesktopTasksLimiter(
transitions,
- desktopModeTaskRepository,
+ desktopRepository,
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
@@ -701,7 +715,17 @@ public abstract class WMShellModule {
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
return new ExitDesktopTaskTransitionHandler(
- transitions, context, interactionJankMonitor, handler);
+ transitions, context, interactionJankMonitor, handler);
+ }
+
+ @WMSingleton
+ @Provides
+ static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
+ Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor
+ ) {
+ return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
}
@WMSingleton
@@ -715,13 +739,14 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+
+ static DesktopRepository provideDesktopRepository(
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
@ShellMainThread CoroutineScope mainScope
) {
- return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+ return new DesktopRepository(context, shellInit, desktopPersistentRepository,
mainScope);
}
@@ -733,12 +758,12 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
+ @DynamicOverride DesktopRepository desktopRepository
) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(new DesktopActivityOrientationChangeHandler(
context, shellInit, shellTaskOrganizer, taskStackListener,
- toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
+ toggleResizeDesktopTaskTransitionHandler, desktopRepository));
}
return Optional.empty();
}
@@ -747,12 +772,12 @@ public abstract class WMShellModule {
@Provides
static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
Context context,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
- return desktopModeTaskRepository.flatMap(repository ->
+ return desktopRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
context, repository, transitions, shellTaskOrganizer, shellInit))
);
@@ -760,6 +785,32 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
+ Context context,
+ Transitions transitions,
+ @DynamicOverride DesktopRepository desktopRepository,
+ FreeformTaskTransitionHandler freeformTaskTransitionHandler,
+ CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
+ InteractionJankMonitor interactionJankMonitor,
+ @ShellMainThread Handler handler
+ ) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)
+ || !ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new DesktopMixedTransitionHandler(
+ context,
+ transitions,
+ desktopRepository,
+ freeformTaskTransitionHandler,
+ closeDesktopTaskTransitionHandler,
+ interactionJankMonitor,
+ handler));
+ }
+
+ @WMSingleton
+ @Provides
static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
Context context,
ShellInit shellInit,
@@ -801,10 +852,11 @@ public abstract class WMShellModule {
static DesktopWindowingEducationTooltipController
provideDesktopWindowingEducationTooltipController(
Context context,
- AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory
+ AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+ DisplayController displayController
) {
return new DesktopWindowingEducationTooltipController(context,
- additionalSystemViewContainerFactory);
+ additionalSystemViewContainerFactory, displayController);
}
@OptIn(markerClass = ExperimentalCoroutinesApi.class)
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
new file mode 100644
index 000000000000..a16c15dfdf1a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.TypedValue
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.app.animation.Interpolators
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.transition.Transitions
+import java.util.function.Supplier
+
+/** The [Transitions.TransitionHandler] that handles transitions for closing desktop mode tasks. */
+class CloseDesktopTaskTransitionHandler
+@JvmOverloads
+constructor(
+ private val context: Context,
+ private val mainExecutor: ShellExecutor,
+ private val animExecutor: ShellExecutor,
+ private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+) : Transitions.TransitionHandler {
+
+ private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
+
+ /** Returns null, as it only handles transitions started from Shell. */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ if (info.type != WindowManager.TRANSIT_CLOSE) return false
+ val animations = mutableListOf<Animator>()
+ val onAnimFinish: (Animator) -> Unit = { animator ->
+ mainExecutor.execute {
+ // Animation completed
+ animations.remove(animator)
+ if (animations.isEmpty()) {
+ // All animations completed, finish the transition
+ runningAnimations.remove(transition)
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ }
+ }
+ }
+ animations +=
+ info.changes
+ .filter {
+ it.mode == WindowManager.TRANSIT_CLOSE &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
+ if (animations.isEmpty()) return false
+ runningAnimations[transition] = animations
+ animExecutor.execute { animations.forEach(Animator::start) }
+ return true
+ }
+
+ private fun createCloseAnimation(
+ change: TransitionInfo.Change,
+ finishTransaction: Transaction,
+ onAnimFinish: (Animator) -> Unit,
+ ): Animator {
+ finishTransaction.hide(change.leash)
+ return AnimatorSet().apply {
+ playTogether(createBoundsCloseAnimation(change), createAlphaCloseAnimation(change))
+ addListener(onEnd = onAnimFinish)
+ }
+ }
+
+ private fun createBoundsCloseAnimation(change: TransitionInfo.Change): Animator {
+ val startBounds = change.startAbsBounds
+ val endBounds =
+ Rect(startBounds).apply {
+ // Scale the end bounds of the window down with an anchor in the center
+ inset(
+ (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
+ (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt()
+ )
+ val offsetY =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ CLOSE_ANIM_OFFSET_Y,
+ context.resources.displayMetrics
+ )
+ .toInt()
+ offset(/* dx= */ 0, offsetY)
+ }
+ return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply {
+ duration = CLOSE_ANIM_DURATION_BOUNDS
+ interpolator = Interpolators.STANDARD_ACCELERATE
+ addUpdateListener { animation ->
+ val animBounds = animation.animatedValue as Rect
+ val animScale = 1 - (1 - CLOSE_ANIM_SCALE) * animation.animatedFraction
+ transactionSupplier
+ .get()
+ .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
+ .setScale(change.leash, animScale, animScale)
+ .apply()
+ }
+ }
+ }
+
+ private fun createAlphaCloseAnimation(change: TransitionInfo.Change): Animator =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = CLOSE_ANIM_DURATION_ALPHA
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ transactionSupplier
+ .get()
+ .setAlpha(change.leash, animation.animatedValue as Float)
+ .apply()
+ }
+ }
+
+ private companion object {
+ const val CLOSE_ANIM_DURATION_BOUNDS = 200L
+ const val CLOSE_ANIM_DURATION_ALPHA = 100L
+ const val CLOSE_ANIM_SCALE = 0.95f
+ const val CLOSE_ANIM_OFFSET_Y = 36.0f
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 59e006879da8..606aa6cd3353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val taskStackListener: TaskStackListenerImpl,
private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
) {
init {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
new file mode 100644
index 000000000000..435019929cbd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.os.Handler
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.MixedTransitionHandler
+import com.android.wm.shell.transition.Transitions
+
+/** The [Transitions.TransitionHandler] coordinates transition handlers in desktop windowing. */
+class DesktopMixedTransitionHandler(
+ private val context: Context,
+ private val transitions: Transitions,
+ private val desktopRepository: DesktopRepository,
+ private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
+ private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
+) : MixedTransitionHandler, FreeformTaskTransitionStarter {
+
+ /** Delegates starting transition to [FreeformTaskTransitionHandler]. */
+ override fun startWindowingModeTransition(
+ targetWindowingMode: Int,
+ wct: WindowContainerTransaction?,
+ ) = freeformTaskTransitionHandler.startWindowingModeTransition(targetWindowingMode, wct)
+
+ /** Delegates starting minimized mode transition to [FreeformTaskTransitionHandler]. */
+ override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder =
+ freeformTaskTransitionHandler.startMinimizedModeTransition(wct)
+
+ /** Starts close transition and handles or delegates desktop task close animation. */
+ override fun startRemoveTransition(wct: WindowContainerTransaction?) {
+ requireNotNull(wct)
+ transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+ }
+
+ /** Returns null, as it only handles transitions started from Shell. */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ val closeChange = findCloseDesktopTaskChange(info)
+ if (closeChange == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: Should have closing desktop task", TAG)
+ return false
+ }
+ if (isLastDesktopTask(closeChange)) {
+ // Dispatch close desktop task animation to the default transition handlers.
+ return dispatchCloseLastDesktopTaskAnimation(
+ transition,
+ info,
+ closeChange,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ }
+ // Animate close desktop task transition with [CloseDesktopTaskTransitionHandler].
+ return closeDesktopTaskTransitionHandler.startAnimation(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ }
+
+ /**
+ * Dispatch close desktop task animation to the default transition handlers. Allows delegating
+ * it to Launcher to animate in sync with show Home transition.
+ */
+ private fun dispatchCloseLastDesktopTaskAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ change: TransitionInfo.Change,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ // Starting the jank trace if closing the last window in desktop mode.
+ interactionJankMonitor.begin(
+ change.leash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
+ )
+ // Dispatch the last desktop task closing animation.
+ return transitions.dispatchTransition(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ { wct ->
+ // Finish the jank trace when closing the last window in desktop mode.
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
+ finishCallback.onTransitionFinished(wct)
+ },
+ /* skip= */ this
+ ) != null
+ }
+
+ private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
+ change.taskInfo?.let {
+ desktopRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
+ } ?: false
+
+ private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
+ if (info.type != WindowManager.TRANSIT_CLOSE) return null
+ return info.changes.firstOrNull { change ->
+ change.mode == WindowManager.TRANSIT_CLOSE &&
+ !change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER) &&
+ change.taskInfo?.taskId != INVALID_TASK_ID &&
+ change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ }
+
+ companion object {
+ private const val TAG = "DesktopMixedTransitionHandler"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index cca750014fc1..73e55b2c4792 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -36,7 +36,7 @@ public interface DesktopMode {
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener,
Executor callbackExecutor);
/**
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 02cbe01d0a03..5a277316ffd4 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
@@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
import com.android.wm.shell.protolog.ShellProtoLogGroup
/** Event logger for logging desktop mode session events */
@@ -41,6 +43,7 @@ class DesktopModeEventLogger {
/* exitReason */ 0,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId)
}
/**
@@ -61,6 +64,7 @@ class DesktopModeEventLogger {
/* exitReason */ exitReason.reason,
/* session_id */ sessionId
)
+ EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId)
}
/**
@@ -76,7 +80,8 @@ class DesktopModeEventLogger {
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -92,7 +97,8 @@ class DesktopModeEventLogger {
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
}
/**
@@ -108,7 +114,46 @@ class DesktopModeEventLogger {
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
- sessionId, taskUpdate)
+ sessionId, taskUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is starting with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
+ }
+
+ /**
+ * Logs that a task resize event is ending with [taskSizeUpdate] within a
+ * Desktop mode [sessionId].
+ */
+ fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) {
+ if (!Flags.enableResizingMetrics()) return
+
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s",
+ sessionId,
+ taskSizeUpdate.instanceId
+ )
+ logTaskSizeUpdated(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ sessionId, taskSizeUpdate
+ )
}
private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
@@ -135,6 +180,56 @@ class DesktopModeEventLogger {
/* visible_task_count */
taskUpdate.visibleTaskCount
)
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ /* task_event */
+ taskEvent,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId,
+ taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+ taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+ /* visible_task_count */
+ taskUpdate.visibleTaskCount
+ )
+ }
+
+ private fun logTaskSizeUpdated(
+ resizingStage: Int,
+ sessionId: Int,
+ taskSizeUpdate: TaskSizeUpdate
+ ) {
+ FrameworkStatsLog.write(
+ DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
+ /* resize_trigger */
+ taskSizeUpdate.resizeTrigger?.trigger ?: ResizeTrigger.UNKNOWN_RESIZE_TRIGGER.trigger,
+ /* resizing_stage */
+ resizingStage,
+ /* input_method */
+ taskSizeUpdate.inputMethod?.method ?: InputMethod.UNKNOWN_INPUT_METHOD.method,
+ /* desktop_mode_session_id */
+ sessionId,
+ /* instance_id */
+ taskSizeUpdate.instanceId,
+ /* uid */
+ taskSizeUpdate.uid,
+ /* task_height */
+ taskSizeUpdate.taskHeight,
+ /* task_width */
+ taskSizeUpdate.taskWidth,
+ /* display_area */
+ taskSizeUpdate.displayArea
+ )
}
companion object {
@@ -163,13 +258,35 @@ class DesktopModeEventLogger {
val visibleTaskCount: Int,
)
+ /**
+ * Describes a task size update (resizing, snapping or maximizing to
+ * stable bounds).
+ *
+ * @property resizeTrigger the trigger for task resize
+ * @property inputMethod the input method for resizing this task
+ * @property instanceId instance id of the task
+ * @property uid uid of the app associated with the task
+ * @property taskHeight height of the task in dp
+ * @property taskWidth width of the task in dp
+ * @property displayArea the display size of the screen in dp
+ */
+ data class TaskSizeUpdate(
+ val resizeTrigger: ResizeTrigger? = null,
+ val inputMethod: InputMethod? = null,
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int,
+ val taskWidth: Int,
+ val displayArea: Int,
+ )
+
// Default value used when the task was not minimized.
@VisibleForTesting
const val UNSET_MINIMIZE_REASON =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
/** The reason a task was minimized. */
- enum class MinimizeReason (val reason: Int) {
+ enum class MinimizeReason(val reason: Int) {
TASK_LIMIT(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
@@ -186,7 +303,7 @@ class DesktopModeEventLogger {
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
/** The reason a task was unminimized. */
- enum class UnminimizeReason (val reason: Int) {
+ enum class UnminimizeReason(val reason: Int) {
UNKNOWN(
FrameworkStatsLog
.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
@@ -250,8 +367,88 @@ class DesktopModeEventLogger {
SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
}
+ /**
+ * Enum ResizeTrigger mapped to the ResizeTrigger definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ResizeTrigger(val trigger: Int) {
+ UNKNOWN_RESIZE_TRIGGER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER
+ ),
+ CORNER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+ ),
+ EDGE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__EDGE_RESIZE_TRIGGER
+ ),
+ TILING_DIVIDER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__TILING_DIVIDER_RESIZE_TRIGGER
+ ),
+ MAXIMIZE_BUTTON(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_BUTTON_RESIZE_TRIGGER
+ ),
+ DOUBLE_TAP_APP_HEADER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DOUBLE_TAP_APP_HEADER_RESIZE_TRIGGER
+ ),
+ DRAG_LEFT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_LEFT_RESIZE_TRIGGER
+ ),
+ DRAG_RIGHT(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_RIGHT_RESIZE_TRIGGER
+ ),
+ SNAP_LEFT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_LEFT_MENU_RESIZE_TRIGGER
+ ),
+ SNAP_RIGHT_MENU(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER
+ ),
+ }
+
+ /**
+ * Enum InputMethod mapped to the InputMethod definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class InputMethod(val method: Int) {
+ UNKNOWN_INPUT_METHOD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ ),
+ TOUCH(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+ ),
+ STYLUS(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+ ),
+ MOUSE(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+ ),
+ TOUCHPAD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCHPAD_INPUT_METHOD
+ ),
+ KEYBOARD(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__KEYBOARD_INPUT_METHOD
+ ),
+ }
+
private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 955fe83d34ee..7b2a5d343861 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -42,8 +42,8 @@ import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-/** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository (
+/** Tracks desktop data for Android Desktop Windowing. */
+class DesktopRepository (
private val context: Context,
shellInit: ShellInit,
private val persistentRepository: DesktopPersistentRepository,
@@ -350,8 +350,17 @@ class DesktopModeTaskRepository (
/** Minimizes the task for [taskId] and [displayId] */
fun minimizeTask(displayId: Int, taskId: Int) {
- logD("Minimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ if (displayId == INVALID_DISPLAY) {
+ // When a task vanishes it doesn't have a displayId. Find the display of the task and
+ // mark it as minimized.
+ getDisplayIdForTask(taskId)?.let {
+ minimizeTask(it, taskId)
+ } ?: 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)
+ }
+
if (Flags.enableDesktopWindowingPersistence()) {
updatePersistentRepository(displayId)
}
@@ -458,10 +467,9 @@ class DesktopModeTaskRepository (
}
}
-
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- pw.println("${prefix}DesktopModeTaskRepository")
+ pw.println("${prefix}DesktopRepository")
dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
@@ -503,10 +511,9 @@ class DesktopModeTaskRepository (
}
companion object {
- private const val TAG = "DesktopModeTaskRepository"
+ private const val TAG = "DesktopRepository"
}
}
private fun <T> Iterable<T>.toDumpString(): String =
joinToString(separator = ", ", prefix = "[", postfix = "]")
-
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 125805c14321..7e06e5ec8b3f 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
@@ -44,7 +44,6 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
-import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -74,7 +73,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
@@ -135,7 +134,7 @@ class DesktopTasksController(
private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
@@ -407,7 +406,7 @@ class DesktopTasksController(
interactionJankMonitor.begin(taskSurface, context, handler,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo.taskId,
+ taskInfo,
dragToDesktopValueAnimator
)
}
@@ -550,7 +549,29 @@ class DesktopTasksController(
/** Move a task to the front */
fun moveTaskToFront(taskId: Int) {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (task == null) moveBackgroundTaskToFront(taskId) else moveTaskToFront(task)
+ }
+
+ /**
+ * Launch a background task in desktop. Note that this should be used when we are already in
+ * desktop. If outside of desktop and want to launch a background task in desktop, use
+ * [moveBackgroundTaskToDesktop] instead.
+ */
+ private fun moveBackgroundTaskToFront(taskId: Int) {
+ logV("moveBackgroundTaskToFront taskId=%s", taskId)
+ val wct = WindowContainerTransaction()
+ // TODO: b/342378842 - Instead of using default display, support multiple displays
+ val taskToMinimize: RunningTaskInfo? =
+ addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
+ wct.startTask(
+ taskId,
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ }.toBundle(),
+ )
+ val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+ addPendingMinimizeTransition(transition, taskToMinimize)
}
/** Move a task to the front */
@@ -558,7 +579,8 @@ class DesktopTasksController(
logV("moveTaskToFront taskId=%s", taskInfo.taskId)
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true)
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo)
+ val taskToMinimize =
+ addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
addPendingMinimizeTransition(transition, taskToMinimize)
@@ -1254,7 +1276,7 @@ class DesktopTasksController(
}
// Desktop Mode is showing and we're launching a new Task - we might need to minimize
// a Task.
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
+ val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
if (taskToMinimize != null) {
addPendingMinimizeTransition(transition, taskToMinimize)
return wct
@@ -1280,7 +1302,8 @@ class DesktopTasksController(
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
- val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
+ val taskToMinimize =
+ addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
addPendingMinimizeTransition(transition, taskToMinimize)
}
}
@@ -1313,14 +1336,11 @@ class DesktopTasksController(
// Remove wallpaper activity when the last active task is removed
removeWallpaperActivity(wct)
}
- taskRepository.addClosingTask(task.displayId, task.taskId)
- // If a CLOSE is triggered on a desktop task, remove the task.
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() &&
- taskRepository.isVisibleTask(task.taskId) &&
- transitionType == TRANSIT_CLOSE
- ) {
- wct.removeTask(task.token)
+
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
+ taskRepository.addClosingTask(task.displayId, task.taskId)
}
+
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
@@ -1425,12 +1445,12 @@ class DesktopTasksController(
private fun addAndGetMinimizeChangesIfNeeded(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskInfo: RunningTaskInfo
+ newTaskId: Int
): RunningTaskInfo? {
if (!desktopTasksLimiter.isPresent) return null
return desktopTasksLimiter
.get()
- .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskInfo)
+ .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskId)
}
private fun addPendingMinimizeTransition(
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 d84349b1ce1f..37bec21730a6 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
@@ -24,6 +24,7 @@ import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import android.window.flags.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
@@ -43,7 +44,7 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver
*/
class DesktopTasksLimiter (
transitions: Transitions,
- private val taskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopRepository,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val maxTasksLimit: Int,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -159,8 +160,10 @@ class DesktopTasksLimiter (
}
@VisibleForTesting
- inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+ inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
override fun onActiveTasksChanged(displayId: Int) {
+ // If back navigation is enabled, we shouldn't remove the leftover tasks
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
val wct = WindowContainerTransaction()
removeLeftoverMinimizedTasks(displayId, wct)
shellTaskOrganizer.applyTransaction(wct)
@@ -208,15 +211,15 @@ class DesktopTasksLimiter (
fun addAndGetMinimizeTaskChangesIfNeeded(
displayId: Int,
wct: WindowContainerTransaction,
- newFrontTaskInfo: RunningTaskInfo,
+ newFrontTaskId: Int,
): RunningTaskInfo? {
ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
- newFrontTaskInfo.taskId)
+ newFrontTaskId)
val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront(
taskRepository.getActiveNonMinimizedOrderedTasks(displayId),
- newFrontTaskInfo.taskId)
+ newFrontTaskId)
val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack)
if (taskToMinimize != null) {
wct.reorder(taskToMinimize.token, false /* onTop */)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 4796c4d0655a..e086e40fb21c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -29,18 +29,19 @@ import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+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
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
- * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
* mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
private val context: Context,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopRepository: DesktopRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
@@ -67,9 +68,29 @@ class DesktopTasksTransitionObserver(
) {
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
-
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
handleBackNavigation(info)
+ removeTaskIfNeeded(info)
+ }
+ }
+
+ private fun removeTaskIfNeeded(info: TransitionInfo) {
+ // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by
+ // checking the transitions.
+ if (!TransitionUtil.isOpeningType(info.type)) return
+ // Remove a task from the repository if the app is launched outside of desktop.
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) continue
+
+ if (desktopRepository.isActiveTask(taskInfo.taskId)
+ && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+ ) {
+ desktopRepository.removeFreeformTask(
+ taskInfo.displayId,
+ taskInfo.taskId
+ )
+ }
}
}
@@ -83,11 +104,11 @@ class DesktopTasksTransitionObserver(
continue
}
- if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
change.mode == TRANSIT_TO_BACK &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
) {
- desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
}
}
}
@@ -114,7 +135,7 @@ class DesktopTasksTransitionObserver(
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
WindowManager.TRANSIT_OPEN -> {
- desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ desktopRepository.wallpaperActivityToken = taskInfo.token
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -124,7 +145,7 @@ class DesktopTasksTransitionObserver(
)
}
WindowManager.TRANSIT_CLOSE ->
- desktopModeTaskRepository.wallpaperActivityToken = null
+ desktopRepository.wallpaperActivityToken = null
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2bc01b2f310e..8e264b2410f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -109,8 +109,8 @@ sealed class DragToDesktopTransitionHandler(
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskId: Int,
- dragToDesktopAnimator: MoveToDesktopAnimator,
+ taskInfo: RunningTaskInfo,
+ dragToDesktopAnimator: MoveToDesktopAnimator
) {
if (inProgress) {
ProtoLog.v(
@@ -137,23 +137,26 @@ sealed class DragToDesktopTransitionHandler(
)
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The home launch done above will result in an attempt to move the task to pip if
+ // applicable, resulting in a broken state. Prevent that here.
+ wct.setDoNotPip(taskInfo.token)
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState =
- if (isSplitTask(taskId)) {
+ if (isSplitTask(taskInfo.taskId)) {
val otherTask =
- getOtherSplitTask(taskId)
+ getOtherSplitTask(taskInfo.taskId)
?: throw IllegalStateException("Expected split task to have a counterpart.")
TransitionState.FromSplit(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask
)
} else {
TransitionState.FromFullscreen(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index a1dfb6862ad3..68a250d02958 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -95,7 +96,25 @@ class AppHandleEducationController(
}
}
.flowOn(backgroundDispatcher)
- .collectLatest { captionState -> showEducation(captionState) }
+ .collectLatest { captionState ->
+ showEducation(captionState)
+ // After showing first tooltip, mark education as viewed
+ appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true)
+ }
+ }
+
+ applicationCoroutineScope.launch {
+ if (isFeatureUsed()) return@launch
+ windowDecorCaptionHandleRepository.captionStateFlow
+ .filter { captionState ->
+ captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+ }
+ .take(1)
+ .flowOn(backgroundDispatcher)
+ .collect {
+ // If user expands app handle, mark user has used the feature
+ appHandleEducationDatastoreRepository.updateFeatureUsedTimestampMillis(true)
+ }
}
}
}
@@ -272,6 +291,13 @@ class AppHandleEducationController(
.map { preferences -> preferences.hasEducationViewedTimestampMillis() }
.distinctUntilChanged()
+ /**
+ * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in
+ * datastore proto object.
+ */
+ private suspend fun isFeatureUsed(): Boolean =
+ appHandleEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis()
+
private fun getSize(@DimenRes resourceId: Int): Int {
if (resourceId == Resources.ID_NULL) return 0
return context.resources.getDimensionPixelSize(resourceId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index f420c5be456f..d21b208df482 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -71,6 +71,37 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
/**
+ * Updates [WindowingEducationProto.educationViewedTimestampMillis_] field in datastore with
+ * current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateEducationViewedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setEducationViewedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearEducationViewedTimestampMillis().build()
+ }
+ }
+ }
+
+ /**
+ * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current
+ * timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences.toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+ } else {
+ preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
+ }
+ }
+ }
+
+ /**
* Updates [AppHandleEducation.appUsageStats] and
* [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
* [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
index eee5aaee3ec3..3379ff2a8c30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
@@ -35,6 +35,7 @@ public class FreeformComponents {
public final ShellTaskOrganizer.TaskListener mTaskListener;
public final Optional<Transitions.TransitionHandler> mTransitionHandler;
public final Optional<Transitions.TransitionObserver> mTransitionObserver;
+ public final Optional<FreeformTaskTransitionStarterInitializer> mTransitionStarterInitializer;
/**
* Creates an instance with the given components.
@@ -42,10 +43,12 @@ public class FreeformComponents {
public FreeformComponents(
ShellTaskOrganizer.TaskListener taskListener,
Optional<Transitions.TransitionHandler> transitionHandler,
- Optional<Transitions.TransitionObserver> transitionObserver) {
+ Optional<Transitions.TransitionObserver> transitionObserver,
+ Optional<FreeformTaskTransitionStarterInitializer> transitionStarterInitializer) {
mTaskListener = taskListener;
mTransitionHandler = transitionHandler;
mTransitionObserver = transitionObserver;
+ mTransitionStarterInitializer = transitionStarterInitializer;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 83cc18baf6cc..73f70118b07d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -24,11 +24,12 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import android.window.flags.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
@@ -48,7 +49,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
@@ -63,13 +64,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
mLaunchAdjacentController = launchAdjacentController;
if (shellInit != null) {
shellInit.addInitCallback(this::onInit, this);
@@ -101,7 +102,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
@@ -120,8 +121,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
- repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+ mDesktopRepository.ifPresent(repository -> {
+ // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
+ || repository.isClosingTask(taskInfo.taskId)) {
+ // A task that's vanishing should be removed:
+ // - If it's closed by the X button which means it's marked as a closing task.
+ repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+ } else {
+ repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, false);
+ repository.minimizeTask(taskInfo.displayId, taskInfo.taskId);
+ }
});
}
mWindowDecorationViewModel.onTaskVanished(taskInfo);
@@ -140,7 +150,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
} else if (repository.isClosingTask(taskInfo.taskId)) {
@@ -172,7 +182,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
- mDesktopModeTaskRepository.ifPresent(repository -> {
+ mDesktopRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 517e20910f6d..6aaf001d46f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -19,16 +19,12 @@ package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
-import android.content.Context;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -40,14 +36,9 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
-import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.util.ArrayList;
import java.util.List;
@@ -59,48 +50,24 @@ import java.util.List;
public class FreeformTaskTransitionHandler
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
private static final int CLOSE_ANIM_DURATION = 400;
- private final Context mContext;
private final Transitions mTransitions;
- private final WindowDecorViewModel mWindowDecorViewModel;
- private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final DisplayController mDisplayController;
- private final InteractionJankMonitor mInteractionJankMonitor;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
- @ShellMainThread
- private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
public FreeformTaskTransitionHandler(
- ShellInit shellInit,
Transitions transitions,
- Context context,
- WindowDecorViewModel windowDecorViewModel,
DisplayController displayController,
ShellExecutor mainExecutor,
- ShellExecutor animExecutor,
- DesktopModeTaskRepository desktopModeTaskRepository,
- InteractionJankMonitor interactionJankMonitor,
- @ShellMainThread Handler handler) {
+ ShellExecutor animExecutor) {
mTransitions = transitions;
- mContext = context;
- mWindowDecorViewModel = windowDecorViewModel;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
mDisplayController = displayController;
- mInteractionJankMonitor = interactionJankMonitor;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
- mHandler = handler;
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- shellInit.addInitCallback(this::onInit, this);
- }
- }
-
- private void onInit() {
- mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
}
@Override
@@ -269,20 +236,12 @@ public class FreeformTaskTransitionHandler
startBounds.top + (animation.getAnimatedFraction() * screenHeight));
t.apply();
});
- if (mDesktopModeTaskRepository.getActiveNonMinimizedTaskCount(
- change.getTaskInfo().displayId) == 1) {
- // Starting the jank trace if closing the last window in desktop mode.
- mInteractionJankMonitor.begin(
- sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
- }
animator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animations.remove(animator);
onAnimFinish.run();
- mInteractionJankMonitor.end(
- CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
}
});
animations.add(animator);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt
new file mode 100644
index 000000000000..98bdf059e738
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.freeform
+
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.windowdecor.WindowDecorViewModel
+
+/**
+ * Sets up [FreeformTaskTransitionStarter] for [WindowDecorViewModel] when shell finishes
+ * initializing.
+ *
+ * Used to extract the setup logic from the starter implementation.
+ */
+class FreeformTaskTransitionStarterInitializer(
+ shellInit: ShellInit,
+ private val windowDecorViewModel: WindowDecorViewModel,
+ private val freeformTaskTransitionStarter: FreeformTaskTransitionStarter
+) {
+ init {
+ shellInit.addInitCallback(::onShellInit, this)
+ }
+
+ /** Sets up [WindowDecorViewModel] transition starter with [FreeformTaskTransitionStarter] */
+ private fun onShellInit() {
+ windowDecorViewModel.setFreeformTaskTransitionStarter(freeformTaskTransitionStarter)
+ }
+}
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 1b9bf2acbb96..dc0bc7816859 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
@@ -304,54 +304,28 @@ public class PipTransition extends PipTransitionController implements
if (pipChange == null) {
return false;
}
- WindowContainerToken pipTaskToken = pipChange.getContainer();
SurfaceControl pipLeash = pipChange.getLeash();
+ Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
- if (pipTaskToken == null || pipLeash == null) {
- return false;
- }
-
- SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
- PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
-
- Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds();
- Rect destinationBounds = pipChange.getEndAbsBounds();
-
- float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat();
-
- // We fake the source rect hint when the one prvided by the app is invalid for
- // the animation with an app icon overlay.
- Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint()
- : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio);
-
- WindowContainerTransaction finishWct = new WindowContainerTransaction();
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-
- final float scale = (float) destinationBounds.width() / animationSrcRectHint.width();
- startTransaction.setWindowCrop(pipLeash, animationSrcRectHint);
- startTransaction.setPosition(pipLeash,
- destinationBounds.left - animationSrcRectHint.left * scale,
- destinationBounds.top - animationSrcRectHint.top * scale);
- startTransaction.setScale(pipLeash, scale, scale);
-
- if (overlayLeash != null) {
+ final Rect destinationBounds = pipChange.getEndAbsBounds();
+ final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
+ if (swipePipToHomeOverlay != null) {
final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
-
- // Overlay needs to be adjusted once a new draw comes in resetting surface transform.
- tx.setScale(overlayLeash, 1f, 1f);
- tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f,
- (destinationBounds.height() - overlaySize) / 2f);
+ // It is possible we reparent the PIP activity to a new PIP task (in multi-activity
+ // apps), so we should also reparent the overlay to the final PIP task.
+ startTransaction.reparent(swipePipToHomeOverlay, pipLeash)
+ .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE)
+ .setScale(swipePipToHomeOverlay, 1f, 1f)
+ .setPosition(swipePipToHomeOverlay,
+ (destinationBounds.width() - overlaySize) / 2f,
+ (destinationBounds.height() - overlaySize) / 2f);
}
- startTransaction.apply();
- tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
- this::onClientDrawAtTransitionEnd);
- finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
-
- // Note that finishWct should be free of any actual WM state changes; we are using
- // it for syncing with the client draw after delayed configuration changes are dispatched.
- finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
+ startTransaction.merge(finishTransaction);
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null /* finishWct */);
+ onClientDrawAtTransitionEnd();
return true;
}
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 03ff1aac794c..95cb3df50977 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
@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
+import android.graphics.Point;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -46,13 +47,14 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -79,14 +81,14 @@ import java.util.function.Consumer;
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
+ RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
TaskStackTransitionObserver.TaskStackTransitionObserverListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
- private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
+ private final Optional<DesktopRepository> mDesktopRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -119,7 +121,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -127,7 +129,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ taskStackListener, activityTaskManager, desktopRepository,
taskStackTransitionObserver, mainExecutor);
}
@@ -137,7 +139,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Optional<DesktopRepository> desktopRepository,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -146,7 +148,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mActivityTaskManager = activityTaskManager;
mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
+ mDesktopRepository = desktopRepository;
mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
@@ -166,7 +168,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
+ mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
mMainExecutor);
@@ -415,14 +417,24 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && mDesktopModeTaskRepository.isPresent()
- && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+ && mDesktopRepository.isPresent()
+ && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = recentTasks.size();
}
+ // 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.
+ if (Flags.enableDesktopWindowingPersistence()
+ && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
+ taskInfo.configuration.windowConfiguration.setAppBounds(
+ taskInfo.lastNonFullscreenBounds);
+ taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
+ taskInfo.lastNonFullscreenBounds.top);
+ }
freeformTasks.add(taskInfo);
- if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
minimizedFreeformTasks.add(taskInfo.taskId);
}
continue;
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 8077aeebf27f..f7ed1dd4606b 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
@@ -52,7 +52,6 @@ import android.util.ArrayMap;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
-import android.view.Display;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction;
@@ -910,6 +909,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"task #" + taskInfo.taskId + " is always_on_top");
return;
}
+ if (TransitionUtil.isClosingType(change.getMode())
+ && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) {
+ // Pinned task is closing as a side effect of the removal of its original Task,
+ // such transition should be handled by PiP. So cancel the merge here.
+ cancel(false /* toHome */, false /* withScreenshots */,
+ "task #" + taskInfo.taskId + " is removed with its original parent");
+ return;
+ }
final boolean isRootTask = taskInfo != null
&& TransitionInfo.isIndependent(change, info);
final boolean isRecentsTask = mRecentsTask != null
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 e8eb10c984af..e527c02e0dec 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
@@ -901,6 +901,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setEnterInstanceId(instanceId);
}
+
+ @Override
+ public void setExcludeImeInsets(boolean exclude) {
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mRootTaskInfo == null) {
+ ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "setExcludeImeInsets: mRootTaskInfo is null");
+ return;
+ }
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "setExcludeImeInsets: root taskId=%s exclude=%s",
+ mRootTaskInfo.taskId, exclude);
+ wct.setExcludeImeInsets(mRootTaskInfo.token, exclude);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
/**
* Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
* launch the non-pipped app as a fullscreen app, otherwise no-op.
@@ -1717,6 +1734,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
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 30d7245436be..e61929fef312 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
@@ -141,10 +141,13 @@ public class MixedTransitionHelper {
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();
// Dispatch the rest of the transition normally. This will most-likely be taken by
// recents or default handler.
mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
- otherStartT, finishTransaction, finishCB, mixedHandler);
+ otherStartT, otherFinishT, finishCB, mixedHandler);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ "forward animation to Pip-Handler.");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index d280dcd252b4..d5e92e6a0e62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1036,9 +1036,14 @@ public class Transitions implements RemoteCallable<Transitions>,
* Gives every handler (in order) a chance to animate until one consumes the transition.
* @return the handler which consumed the transition.
*/
- TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
- @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) {
+ public TransitionHandler dispatchTransition(
+ @NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull TransitionFinishCallback finishCB,
+ @Nullable TransitionHandler skip
+ ) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
if (mHandlers.get(i) == skip) continue;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
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 3330f968332c..bf175b71ca05 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
@@ -103,7 +103,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -155,7 +155,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
private final ShellController mShellController;
private final Context mContext;
private final @ShellMainThread Handler mMainHandler;
@@ -227,7 +227,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -288,7 +288,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5daa3eee340b..a44d91143ffa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -93,7 +93,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -193,14 +193,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
- private final DesktopModeTaskRepository mDesktopRepository;
+ private final DesktopRepository mDesktopRepository;
DesktopModeWindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -232,7 +232,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -824,6 +824,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
false /* ignoreVisibility */);
relayoutParams.mCaptionTopPadding = systemBarInsets.top;
+ relayoutParams.mIsInsetSource = false;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
@@ -1590,7 +1591,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopModeTaskRepository desktopRepository,
+ DesktopRepository desktopRepository,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
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 000beba125b0..f8aed412e3fb 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
@@ -346,7 +346,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
RelayoutResult<T> outResult, Rect taskBounds) {
- if (!mIsCaptionVisible) {
+ if (!mIsCaptionVisible || !params.mIsInsetSource) {
if (mWindowDecorationInsets != null) {
mWindowDecorationInsets.remove(wct);
mWindowDecorationInsets = null;
@@ -724,6 +724,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ boolean mIsInsetSource = true;
@InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
@@ -743,6 +744,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mIsInsetSource = true;
mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
index 98413ee96133..a9a16bc72779 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -30,9 +30,13 @@ import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -44,7 +48,8 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem
class DesktopWindowingEducationTooltipController(
private val context: Context,
private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
-) {
+ private val displayController: DisplayController,
+) : OnDisplayChangingListener {
// TODO: b/369384567 - Set tooltip color scheme to match LT/DT of app theme
private var tooltipView: View? = null
private var animator: PhysicsAnimator<View>? = null
@@ -53,6 +58,20 @@ class DesktopWindowingEducationTooltipController(
}
private var popupWindow: AdditionalSystemViewContainer? = null
+ override fun onDisplayChange(
+ displayId: Int,
+ fromRotation: Int,
+ toRotation: Int,
+ newDisplayAreaInfo: DisplayAreaInfo?,
+ t: WindowContainerTransaction?
+ ) {
+ // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+ // [toRotation] can be one of the [@Surface.Rotation] values.
+ if ((fromRotation % 2 == toRotation % 2)) return
+ hideEducationTooltip()
+ // TODO: b/370820018 - Update tooltip position on orientation change instead of dismissing
+ }
+
/**
* Shows education tooltip.
*
@@ -64,6 +83,7 @@ class DesktopWindowingEducationTooltipController(
tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
animator = createAnimator()
animateShowTooltipTransition()
+ displayController.addDisplayChangingController(this)
}
/** Hide the current education view if visible */
@@ -145,6 +165,7 @@ class DesktopWindowingEducationTooltipController(
animator = null
popupWindow?.releaseView()
popupWindow = null
+ displayController.removeDisplayChangingController(this)
}
private fun createTooltipPopupWindow(
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index 29a9f1050b25..7412c1dadc04 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,7 +15,7 @@
//
package {
- default_team: "trendy_team_app_compat",
+ default_team: "trendy_team_lse_app_compat",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index d03d7799d675..d1bf6acf785d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -183,12 +183,6 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 312446524)
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
@Test
@FlakyTest(bugId = 336510055)
override fun entireScreenCovered() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index f8f0db930e6c..0373bbd43043 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -34,6 +36,12 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import android.graphics.Insets;
import android.graphics.Point;
+import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -47,6 +55,7 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,11 +70,16 @@ import java.util.concurrent.Executor;
*/
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private SurfaceControl.Transaction mT;
@Mock
private ShellInit mShellInit;
+ @Mock
+ private IWindowManager mWm;
+ private DisplayImeController mDisplayImeController;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -73,7 +87,8 @@ public class DisplayImeControllerTest extends ShellTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
+ mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null,
+ new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
@@ -84,8 +99,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
}
}, mExecutor) {
@Override
- void removeImeSurface(int displayId) { }
- }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
+ void removeImeSurface(int displayId) {
+ }
+ };
+ mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
@Test
@@ -95,12 +112,14 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
verifyZeroInteractions(mExecutor);
}
@Test
public void insetsChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsChanged(insetsStateWithIme(false));
verifyZeroInteractions(mExecutor);
}
@@ -117,7 +136,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
verifyZeroInteractions(mExecutor);
}
+ // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
+ // this test is obsolete
@Test
+ @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void reappliesVisibilityToChangedLeash() {
verifyZeroInteractions(mT);
mPerDisplay.mImeShowing = false;
@@ -136,6 +158,7 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
public void insetsControlChanged_updateImeSourceControl() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
assertNotNull(mPerDisplay.mImeSourceControl);
@@ -143,6 +166,19 @@ public class DisplayImeControllerTest extends ShellTestCase {
assertNull(mPerDisplay.mImeSourceControl);
}
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() {
+ var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
+ mDisplayImeController.addPositionProcessor(mockPp);
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(true);
+ verify(mockPp).onImeRequested(anyInt(), eq(true));
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(false);
+ verify(mockPp).onImeRequested(anyInt(), eq(false));
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
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
new file mode 100644
index 000000000000..9b4cc17e19d9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+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.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.ShellExecutor
+import java.util.function.Supplier
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.mock
+
+/**
+ * Test class for [CloseDesktopTaskTransitionHandler]
+ *
+ * Usage: atest WMShellUnitTests:CloseDesktopTaskTransitionHandlerTest
+ */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var closingTaskLeash: SurfaceControl
+
+ private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
+
+ private lateinit var handler: CloseDesktopTaskTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler =
+ CloseDesktopTaskTransitionHandler(
+ context,
+ testExecutor,
+ testExecutor,
+ transactionSupplier
+ )
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(handler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_openTransition_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate open transition", animates)
+ }
+
+ @Test
+ fun startAnimation_closeTransitionFullscreenTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate fullscreen task close transition", animates)
+ }
+
+ @Test
+ fun startAnimation_closeTransitionOpeningFreeformTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ changeMode = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate opening freeform task close transition", animates)
+ }
+
+ @Test
+ fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should animate closing freeform task close transition", animates)
+ }
+
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_CLOSE,
+ changeMode: Int = WindowManager.TRANSIT_CLOSE,
+ task: RunningTaskInfo
+ ): TransitionInfo =
+ TransitionInfo(type, 0 /* flags */).apply {
+ addChange(
+ TransitionInfo.Change(mock(), closingTaskLeash).apply {
+ mode = changeMode
+ parent = null
+ taskInfo = task
+ }
+ )
+ }
+
+ private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(windowingMode)
+ .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 628c9cdd9339..3e9c732b9c3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -98,7 +98,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var testScope: CoroutineScope
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -116,7 +116,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
taskRepository =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
new file mode 100644
index 000000000000..07de0716200c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+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.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
+import com.android.wm.shell.transition.Transitions
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopMixedTransitionHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopMixedTransitionHandlerTest
+ */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopMixedTransitionHandlerTest : ShellTestCase() {
+
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var desktopRepository: DesktopRepository
+ @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
+ @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var mockHandler: Handler
+ @Mock lateinit var closingTaskLeash: SurfaceControl
+
+ private lateinit var mixedHandler: DesktopMixedTransitionHandler
+
+ @Before
+ fun setUp() {
+ mixedHandler =
+ DesktopMixedTransitionHandler(
+ context,
+ transitions,
+ desktopRepository,
+ freeformTaskTransitionHandler,
+ closeDesktopTaskTransitionHandler,
+ interactionJankMonitor,
+ mockHandler
+ )
+ }
+
+ @Test
+ fun startWindowingModeTransition_callsFreeformTaskTransitionHandler() {
+ val windowingMode = WINDOWING_MODE_FULLSCREEN
+ val wct = WindowContainerTransaction()
+
+ mixedHandler.startWindowingModeTransition(windowingMode, wct)
+
+ verify(freeformTaskTransitionHandler).startWindowingModeTransition(windowingMode, wct)
+ }
+
+ @Test
+ fun startMinimizedModeTransition_callsFreeformTaskTransitionHandler() {
+ val wct = WindowContainerTransaction()
+ whenever(freeformTaskTransitionHandler.startMinimizedModeTransition(any()))
+ .thenReturn(mock())
+
+ mixedHandler.startMinimizedModeTransition(wct)
+
+ verify(freeformTaskTransitionHandler).startMinimizedModeTransition(wct)
+ }
+
+ @Test
+ fun startRemoveTransition_startsCloseTransition() {
+ val wct = WindowContainerTransaction()
+
+ mixedHandler.startRemoveTransition(wct)
+
+ verify(transitions).startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler)
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(mixedHandler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_withoutClosingDesktopTask_returnsFalse() {
+ val transition = mock<IBinder>()
+ val transitionInfo =
+ createTransitionInfo(
+ changeMode = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ )
+ whenever(freeformTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any()))
+ .thenReturn(true)
+
+ val started = mixedHandler.startAnimation(
+ transition = transition,
+ info = transitionInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not start animation without closing desktop task", started)
+ }
+
+ @Test
+ fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
+ val transition = mock<IBinder>()
+ val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2)
+ whenever(
+ closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+ )
+ .thenReturn(true)
+
+ val started = mixedHandler.startAnimation(
+ transition = transition,
+ info = transitionInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should delegate animation to close transition handler", started)
+ verify(closeDesktopTaskTransitionHandler)
+ .startAnimation(eq(transition), eq(transitionInfo), any(), any(), any())
+ }
+
+ @Test
+ fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
+ val transition = mock<IBinder>()
+ val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
+ whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1)
+ whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
+ .thenReturn(mock())
+
+ mixedHandler.startAnimation(
+ transition = transition,
+ info = transitionInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ eq(transitionInfo),
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler)
+ )
+ verify(interactionJankMonitor)
+ .begin(
+ closingTaskLeash,
+ context,
+ mockHandler,
+ CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+ )
+ }
+
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_CLOSE,
+ changeMode: Int = WindowManager.TRANSIT_CLOSE,
+ task: RunningTaskInfo
+ ): TransitionInfo =
+ TransitionInfo(type, 0 /* flags */).apply {
+ addChange(
+ TransitionInfo.Change(mock(), closingTaskLeash).apply {
+ mode = changeMode
+ parent = null
+ taskInfo = task
+ }
+ )
+ }
+
+ private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(windowingMode)
+ .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index ca972296e8d4..d7a132dfa1be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,13 +16,22 @@
package com.android.wm.shell.desktopmode
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.ShellTestCase
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
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
@@ -34,14 +43,19 @@ import org.mockito.kotlin.eq
/**
* Tests for [DesktopModeEventLogger].
*/
-class DesktopModeEventLoggerTest {
+class DesktopModeEventLoggerTest : ShellTestCase() {
private val desktopModeEventLogger = DesktopModeEventLogger()
@JvmField
- @Rule
+ @Rule(order = 0)
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java).build()!!
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(EventLogTags::class.java).build()!!
+
+ @JvmField
+ @Rule(order = 1)
+ val setFlagsRule = SetFlagsRule()
@Test
fun logSessionEnter_enterReason() = runBlocking {
@@ -60,6 +74,11 @@ class DesktopModeEventLoggerTest {
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellEnterDesktopMode(
+ eq(EnterReason.UNKNOWN_ENTER.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -79,6 +98,11 @@ class DesktopModeEventLoggerTest {
eq(SESSION_ID)
)
}
+ verify {
+ EventLogTags.writeWmShellExitDesktopMode(
+ eq(ExitReason.UNKNOWN_EXIT.reason),
+ eq(SESSION_ID))
+ }
}
@Test
@@ -108,6 +132,22 @@ class DesktopModeEventLoggerTest {
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -137,6 +177,22 @@ class DesktopModeEventLoggerTest {
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -167,6 +223,22 @@ class DesktopModeEventLoggerTest {
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -200,6 +272,22 @@ class DesktopModeEventLoggerTest {
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(MinimizeReason.TASK_LIMIT.reason),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT))
+ }
}
@Test
@@ -233,9 +321,83 @@ class DesktopModeEventLoggerTest {
/* visible_task_count */
eq(TASK_COUNT))
}
+
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(SESSION_ID),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UnminimizeReason.TASKBAR_TAP.reason),
+ eq(TASK_COUNT))
+ }
}
- companion object {
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking {
+ desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate())
+
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ /* resize_trigger */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER),
+ /* resizing_stage */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
+ /* input_method */
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ /* desktop_mode_session_id */
+ eq(SESSION_ID),
+ /* instance_id */
+ eq(TASK_SIZE_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_SIZE_UPDATE.uid),
+ /* task_height */
+ eq(TASK_SIZE_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_SIZE_UPDATE.taskWidth),
+ /* display_area */
+ eq(TASK_SIZE_UPDATE.displayArea),
+ )
+ }
+ }
+
+ private companion object {
private const val SESSION_ID = 1
private const val TASK_ID = 1
private const val TASK_UID = 1
@@ -244,16 +406,40 @@ class DesktopModeEventLoggerTest {
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
private const val TASK_COUNT = 1
+ private const val DISPLAY_AREA = 1000
private val TASK_UPDATE = TaskUpdate(
TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
visibleTaskCount = TASK_COUNT,
)
+ private val TASK_SIZE_UPDATE = TaskSizeUpdate(
+ resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
+ private fun createTaskSizeUpdate(
+ resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ ) = TaskSizeUpdate(
+ resizeTrigger,
+ inputMethod,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
private fun createTaskUpdate(
minimizeReason: MinimizeReason? = null,
unminimizeReason: UnminimizeReason? = null,
) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
unminimizeReason, TASK_COUNT)
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 794f9d819f4b..55b9724795e1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -56,9 +56,9 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
-class DesktopModeTaskRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest : ShellTestCase() {
- private lateinit var repo: DesktopModeTaskRepository
+ private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
@@ -71,7 +71,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+ repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
)
@@ -820,6 +820,18 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
@Test
+ fun minimizeTask_withInvalidDisplay_minimizesCorrectTask() {
+ repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 0)
+ repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 0)
+
+ repo.minimizeTask(displayId = INVALID_DISPLAY, taskId = 0)
+
+ assertThat(repo.isMinimizedTask(taskId = 0)).isTrue()
+ assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
+ assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
+ }
+
+ @Test
fun unminimizeTask_unminimizesTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
@@ -928,7 +940,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
}
- class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
+ class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
override fun onActiveTasksChanged(displayId: Int) {
@@ -940,7 +952,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
}
- class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+ class TestVisibilityListener : DesktopRepository.VisibleTasksListener {
var visibleTasksCountOnDefaultDisplay = 0
var visibleTasksCountOnSecondaryDisplay = 0
@@ -968,4 +980,4 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
}
-}
+} \ No newline at end of file
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 8870846efed4..cf53bf048d69 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
@@ -201,7 +201,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var taskRepository: DesktopRepository
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
@@ -232,7 +232,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -1350,6 +1350,32 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveTaskToFront_backgroundTask_launchesTask() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+
+ controller.moveTaskToFront(task.taskId)
+
+ val wct = getLatestWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val task = createTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+
+ controller.moveTaskToFront(task.taskId)
+
+ val wct = getLatestWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
+ wct.assertReorderAt(0, freeformTasks[0], toTop = false)
+ wct.assertLaunchTaskAt(1, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2075,11 +2101,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
- )
- fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -2112,8 +2135,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
+ fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -2122,11 +2144,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
taskRepository.wallpaperActivityToken = MockToken().token()
@@ -2149,11 +2168,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2165,7 +2181,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() {
+ fun handleRequest_backTransition_multipleTasks_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2211,11 +2227,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_withBackNav_removesWallpaper() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2231,11 +2244,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -2244,22 +2254,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
+ fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -2268,11 +2264,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
taskRepository.wallpaperActivityToken = MockToken().token()
@@ -2282,26 +2275,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_singleTaskWithToken_removesWallpaperAndTask() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
@@ -2313,11 +2288,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2328,25 +2300,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasks_withWallpaper_withBackNav_removesTask() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNotNull(result, "Should handle request")
- result.assertRemoveAt(index = 0, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksFlagEnabled_noBackNav_doesNotHandle() {
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2357,28 +2312,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2392,28 +2327,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_multipleTasksOneNonMinimized_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() {
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -2427,11 +2342,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_closeTransition_minimizadTask_withWallpaper_withBackNav_removesWallpaper() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
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 045e07796cb8..596b76dbdb2e 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
@@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.os.Binder
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -33,6 +35,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
@@ -89,7 +92,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+ private lateinit var desktopTaskRepo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var testScope: CoroutineScope
@@ -103,7 +106,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
desktopTaskRepo =
- DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
@@ -243,6 +246,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
@@ -256,6 +260,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
@@ -265,6 +270,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -283,6 +289,20 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.onActiveTasksChanged(DEFAULT_DISPLAY)
+
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
@@ -291,7 +311,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = DEFAULT_DISPLAY,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
@@ -307,7 +327,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = DEFAULT_DISPLAY,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isEqualTo(tasks.first())
assertThat(wct.hierarchyOps.size).isEqualTo(1)
@@ -325,7 +345,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
displayId = 0,
wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ newFrontTaskId = setUpFreeformTask().taskId)
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index c989d1640f80..598df34a310d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -18,11 +18,13 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
@@ -60,7 +62,7 @@ class DesktopTasksTransitionObserverTest {
private val transitions = mock<Transitions>()
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
- private val taskRepository = mock<DesktopModeTaskRepository>()
+ private val taskRepository = mock<DesktopRepository>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
@@ -110,6 +112,24 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeTasks_onTaskFullscreenLaunch_taskRemovedFromRepo() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+ whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenTransition(task),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
+ verify(taskRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?
): TransitionInfo {
@@ -125,11 +145,26 @@ class DesktopTasksTransitionObserverTest {
}
}
- private fun createTaskInfo(id: Int) =
+ private fun createOpenTransition(
+ task: RunningTaskInfo?
+ ): TransitionInfo {
+ return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_OPEN
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
+ private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
RunningTaskInfo().apply {
taskId = id
displayId = DEFAULT_DISPLAY
- configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ configuration.windowConfiguration.windowingMode = windowingMode
token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
baseIntent = Intent().apply {
component = ComponentName("package", "component.name")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index d9387d2f08dd..230f7e6912ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -581,7 +581,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator)
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index aad31a6d57ba..1e105d9588ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -46,6 +46,7 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,16 +55,14 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/**
- * Tests of [AppHandleEducationController]
- * Usage: atest AppHandleEducationControllerTest
- */
+/** Tests of [AppHandleEducationController] Usage: atest AppHandleEducationControllerTest */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -190,6 +189,36 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleExpanded_shouldMarkFeatureViewed() =
+ testScope.runTest {
+ setShouldShowAppHandleEducation(false)
+
+ // Simulate app handle visible and expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for some time before verifying
+ waitForBufferDelay()
+
+ verify(mockDataStoreRepository, times(1)).updateFeatureUsedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_showFirstTooltip_shouldMarkEducationViewed() =
+ testScope.runTest {
+ // App handle is visible. Should show education tooltip.
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockDataStoreRepository, times(1)).updateEducationViewedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
@@ -207,6 +236,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded after timeout. Should not show
@@ -228,6 +258,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded twice. Should show second
@@ -249,6 +280,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
testScope.runTest {
// After first tooltip is dismissed, app handle is not expanded. Should not show second
@@ -266,6 +298,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible. Should show third
@@ -283,6 +316,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible after timeout. Should not
@@ -304,6 +338,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible twice. Should show third
@@ -324,6 +359,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
testScope.runTest {
// After first two tooltips are dismissed, app header is visible but expanded. Should not
@@ -363,6 +399,7 @@ class AppHandleEducationControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
testScope.runTest {
// After first tooltip is dismissed, app handle is expanded. Should show second education
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
index 1c1c65083e6b..c2865441d7a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
@@ -109,6 +109,24 @@ class AppHandleEducationDatastoreRepositoryTest {
assertThat(result).isEqualTo(windowingEducationProto)
}
+ @Test
+ fun updateEducationViewedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateEducationViewedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasEducationViewedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
+ @Test
+ fun updateFeatureUsedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateFeatureUsedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasFeatureUsedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
companion object {
private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb"
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 763d0153071e..956ef14bf2ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -18,15 +18,19 @@ package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,7 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -69,7 +73,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Mock
private SurfaceControl mMockSurfaceControl;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
private FreeformTaskListener mFreeformTaskListener;
@@ -85,7 +89,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mContext,
mShellInit,
mTaskOrganizer,
- Optional.of(mDesktopModeTaskRepository),
+ Optional.of(mDesktopRepository),
mLaunchAdjacentController,
mWindowDecorViewModel);
}
@@ -98,7 +102,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopModeTaskRepository)
+ verify(mDesktopRepository)
.addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
}
@@ -110,7 +114,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopModeTaskRepository, never())
+ verify(mDesktopRepository, never())
.addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
}
@@ -139,6 +143,40 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(true);
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ public void onTaskVanished_nonClosingTask_isMinimized() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ public void onTaskVanished_closingTask_isNotMinimized() {
+ ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
+ }
+
@After
public void tearDown() {
mMockitoSession.finishMocking();
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 753d4cd153ee..9b73d53e0639 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
@@ -50,6 +51,7 @@ import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
@@ -68,7 +70,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -107,7 +109,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -144,7 +146,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -303,8 +305,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -342,8 +344,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -382,8 +384,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -410,10 +412,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
- when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -441,6 +443,40 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+
+ t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
+ t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
+ setRawList(t1, t2);
+
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ assertEquals(1, recentTasks.size());
+ GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+
+ // Check bounds
+ assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 0).configuration.windowConfiguration.getAppBounds());
+ assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 1).configuration.windowConfiguration.getAppBounds());
+
+ // Check position in parent
+ assertEquals(new Point(t1.lastNonFullscreenBounds.left,
+ t1.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(0).positionInParent);
+ assertEquals(new Point(t2.lastNonFullscreenBounds.left,
+ t2.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(1).positionInParent);
+ }
+
+ @Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -623,6 +659,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
info.taskId = taskId;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 769acf7fdfde..0effc3e3d6b8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -48,7 +48,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -82,7 +82,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private DesktopRepository mDesktopRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -120,7 +120,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
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 5ae4ca839d61..83bd15b79186 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
@@ -87,7 +87,7 @@ import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository
+import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -161,7 +161,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockSplitScreenController: SplitScreenController
- @Mock private lateinit var mockDesktopRepository: DesktopModeTaskRepository
+ @Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var displayInsetsController: DisplayInsetsController
@Mock private lateinit var mockSyncQueue: SyncTransactionQueue
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3e7f3bdd72a2..8c00dc0ff496 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,7 +106,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -163,7 +163,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private DesktopModeTaskRepository mMockDesktopRepository;
+ private DesktopRepository mMockDesktopRepository;
@Mock
private Choreographer mMockChoreographer;
@Mock
@@ -619,6 +619,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ public void updateRelayoutParams_header_notAnInsetsSourceInFullyImmersive() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ true,
+ new InsetsState());
+
+ assertThat(relayoutParams.mIsInsetSource).isFalse();
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void updateRelayoutParams_header_statusBarInvisible_captionVisible() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 94cabc492277..54dd15baa4c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -650,6 +650,57 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
+ public void testRelayout_notAnInsetsSource_doesNotAddInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Never added.
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
+ verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ public void testRelayout_notAnInsetsSource_hadInsetsBefore_removesInsets() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .setBounds(new Rect(0, 0, 1000, 1000))
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = true;
+ windowDecor.relayout(taskInfo);
+
+ mRelayoutParams.mIsCaptionVisible = true;
+ mRelayoutParams.mIsInsetSource = false;
+ windowDecor.relayout(taskInfo);
+
+ // Insets should be removed.
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
+ @Test
public void testClose_withExistingInsets_insetsRemoved() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
index 5594981135b1..67497764655b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -24,12 +24,16 @@ import android.testing.TestableContext
import android.testing.TestableLooper
import android.testing.TestableResources
import android.view.MotionEvent
+import android.view.Surface.ROTATION_180
+import android.view.Surface.ROTATION_90
import android.view.View
import android.view.WindowManager
import android.widget.TextView
+import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection
import com.google.common.truth.Truth.assertThat
@@ -42,9 +46,11 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -52,6 +58,8 @@ import org.mockito.kotlin.verify
class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
@Mock private lateinit var mockWindowManager: WindowManager
@Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockPopupWindow: AdditionalSystemViewContainer
private lateinit var testableResources: TestableResources
private lateinit var testableContext: TestableContext
private lateinit var tooltipController: DesktopWindowingEducationTooltipController
@@ -69,7 +77,8 @@ class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager)
tooltipController =
- DesktopWindowingEducationTooltipController(testableContext, mockViewContainerFactory)
+ DesktopWindowingEducationTooltipController(
+ testableContext, mockViewContainerFactory, mockDisplayController)
}
@Test
@@ -218,6 +227,25 @@ class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
verify(mockLambda).invoke()
}
+ @Test
+ fun showEducationTooltip_displayRotationChanged_hidesTooltip() {
+ whenever(
+ mockViewContainerFactory.create(any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(mockPopupWindow)
+ val tooltipViewConfig = createTooltipConfig()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+ tooltipController.onDisplayChange(
+ /* displayId= */ 123,
+ /* fromRotation= */ ROTATION_90,
+ /* toRotation= */ ROTATION_180,
+ /* newDisplayAreaInfo= */ null,
+ WindowContainerTransaction())
+
+ verify(mockPopupWindow, times(1)).releaseView()
+ verify(mockDisplayController, atLeastOnce()).removeDisplayChangingController(any())
+ }
+
private fun createTooltipConfig(
@LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip,
tooltipViewGlobalCoordinates: Point = Point(0, 0),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index a39f30bbad1f..385fbfe1a86a 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -75,6 +75,7 @@ cc_library {
"BigBufferStream.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "CursorWindow.cpp",
"FileStream.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
@@ -113,7 +114,6 @@ cc_library {
srcs: [
"BackupData.cpp",
"BackupHelpers.cpp",
- "CursorWindow.cpp",
],
shared_libs: [
"libbase",
@@ -147,11 +147,6 @@ cc_library {
"libz",
],
},
- host_linux: {
- srcs: [
- "CursorWindow.cpp",
- ],
- },
windows: {
enabled: true,
},
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index cbb1e8f82838..abf2b0a91642 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -139,6 +139,7 @@ fail_silent:
return UNKNOWN_ERROR;
}
+#ifdef __linux__
status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) {
*outWindow = nullptr;
@@ -240,6 +241,7 @@ fail:
fail_silent:
return UNKNOWN_ERROR;
}
+#endif
status_t CursorWindow::clear() {
if (mReadOnly) {
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index c2eac12eb77d..0996355cd2c4 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -23,7 +23,9 @@
#include <string>
#include "android-base/stringprintf.h"
+#ifdef __linux__
#include "binder/Parcel.h"
+#endif
#include "utils/String8.h"
#include "android-base/mapped_file.h"
@@ -82,9 +84,11 @@ public:
~CursorWindow();
static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow);
+#ifdef __linux__
static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow);
status_t writeToParcel(Parcel* parcel);
+#endif
inline String8 name() { return mName; }
inline size_t size() { return mSize; }
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index bb0fc41acd30..bc269fedddfe 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,7 +16,7 @@ package com.google.android.appfunctions.sidecar {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -45,12 +45,12 @@ package com.google.android.appfunctions.sidecar {
method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_CANCELLED = 6; // 0x6
field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 6; // 0x6
+ field public static final int RESULT_DISABLED = 5; // 0x5
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_TIMED_OUT = 5; // 0x5
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 6023c977bd76..6e91de6bbcf2 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.CancellationSignal;
+import android.util.Log;
import java.util.function.Consumer;
@@ -143,7 +144,11 @@ public abstract class AppFunctionService extends Service {
*/
@MainThread
@Deprecated
- public abstract void onExecuteFunction(
+ public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Log.w(
+ "AppFunctionService",
+ "Calling deprecated default implementation of onExecuteFunction");
+ }
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index c7ce95bab7a5..d87fec7985e9 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -73,11 +73,14 @@ public final class ExecuteAppFunctionResponse {
*/
public static final int RESULT_INVALID_ARGUMENT = 4;
- /** The operation was timed out. */
- public static final int RESULT_TIMED_OUT = 5;
-
/** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 6;
+ public static final int RESULT_DISABLED = 5;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ */
+ public static final int RESULT_CANCELLED = 6;
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -236,7 +239,6 @@ public final class ExecuteAppFunctionResponse {
RESULT_APP_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
- RESULT_TIMED_OUT,
RESULT_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 27add3542c01..430519606d9b 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -141,6 +141,13 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context)
return;
}
+ if (!RenderThread::isCurrent()) {
+ // releaseQueueOwnership needs to run on RenderThread to prevent multithread calling
+ // setBackendTextureState will operate skia resource cache which need single owner
+ RenderThread::getInstance().queue().post([this, context]() { releaseQueueOwnership(context); });
+ return;
+ }
+
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp
index 30f401ef5f01..ea955e25dc2f 100644
--- a/libs/hwui/Gainmap.cpp
+++ b/libs/hwui/Gainmap.cpp
@@ -15,12 +15,37 @@
*/
#include "Gainmap.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkImagePriv.h>
+#include <SkPaint.h>
+
+#include "HardwareBitmapUploader.h"
+
namespace android::uirenderer {
sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
auto gainmap = sp<Gainmap>::make();
gainmap->info = srcGainmap->info;
- const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ if (skSrcBitmap.info().colorType() == kAlpha_8_SkColorType &&
+ !HardwareBitmapUploader::hasAlpha8Support()) {
+ // The regular Bitmap::allocateHardwareBitmap will do a conversion that preserves channels,
+ // so alpha8 maps to the alpha channel of rgba. However, for gainmaps we will interpret
+ // the data of an rgba buffer differently as we'll only look at the rgb channels
+ // So we need to map alpha8 to rgbx_8888 essentially
+ SkBitmap bitmap;
+ bitmap.allocPixels(skSrcBitmap.info().makeColorType(kN32_SkColorType));
+ SkCanvas canvas(bitmap);
+ SkPaint paint;
+ const float alphaToOpaque[] = {0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 255};
+ paint.setColorFilter(SkColorFilters::Matrix(alphaToOpaque, SkColorFilters::Clamp::kNo));
+ canvas.drawImage(SkMakeImageFromRasterBitmap(skSrcBitmap, kNever_SkCopyPixelsMode), 0, 0,
+ SkSamplingOptions{}, &paint);
+ skSrcBitmap = bitmap;
+ }
sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
if (!skBitmap.get()) {
return nullptr;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 588463c49497..e074a27db38f 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -501,18 +501,13 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
{
- ATRACE_NAME("getAndroidGainmap");
- if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+ ATRACE_NAME("getGainmapAndroidCodec");
+ if (!mCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec)) {
return SkCodec::kSuccess;
}
}
- auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
- if (!gainmapCodec) {
- ALOGW("Failed to create codec for gainmap stream");
- return SkCodec::kInvalidInput;
- }
ImageDecoder decoder{std::move(gainmapCodec)};
// Gainmap inherits the origin of the containing image
decoder.mOverrideOrigin.emplace(getOrigin());
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 708f96e5a070..7eb849fe6e3d 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -159,6 +159,14 @@ public:
return SkSamplingOptions(this->filterMode());
}
+ void setVariationOverride(minikin::VariationSettings&& varSettings) {
+ mFontVariationOverride = std::move(varSettings);
+ }
+
+ const minikin::VariationSettings& getFontVariationOverride() const {
+ return mFontVariationOverride;
+ }
+
// The Java flags (Paint.java) no longer fit into the native apis directly.
// These methods handle converting to and from them and the native representations
// in android::Paint.
@@ -179,6 +187,7 @@ private:
float mLetterSpacing = 0;
float mWordSpacing = 0;
std::vector<minikin::FontFeature> mFontFeatureSettings;
+ minikin::VariationSettings mFontVariationOverride;
uint32_t mMinikinLocaleListId;
std::optional<minikin::FamilyVariant> mFamilyVariant;
uint32_t mHyphenEdit = 0;
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index c32ea01db7b7..6dfcedc3d918 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -39,6 +39,7 @@ Paint::Paint(const Paint& paint)
, mLetterSpacing(paint.mLetterSpacing)
, mWordSpacing(paint.mWordSpacing)
, mFontFeatureSettings(paint.mFontFeatureSettings)
+ , mFontVariationOverride(paint.mFontVariationOverride)
, mMinikinLocaleListId(paint.mMinikinLocaleListId)
, mFamilyVariant(paint.mFamilyVariant)
, mHyphenEdit(paint.mHyphenEdit)
@@ -59,6 +60,7 @@ Paint& Paint::operator=(const Paint& other) {
mLetterSpacing = other.mLetterSpacing;
mWordSpacing = other.mWordSpacing;
mFontFeatureSettings = other.mFontFeatureSettings;
+ mFontVariationOverride = other.mFontVariationOverride;
mMinikinLocaleListId = other.mMinikinLocaleListId;
mFamilyVariant = other.mFamilyVariant;
mHyphenEdit = other.mHyphenEdit;
@@ -76,6 +78,7 @@ bool operator==(const Paint& a, const Paint& b) {
return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
+ a.mFontVariationOverride == b.mFontVariationOverride &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 785aef312072..49a7f73fb3a3 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -183,14 +183,8 @@ static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize,
needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
}
-static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+static bool decodeGainmap(std::unique_ptr<SkAndroidCodec> codec, const SkGainmapInfo& gainmapInfo,
sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
- std::unique_ptr<SkAndroidCodec> codec;
- codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
- if (!codec) {
- ALOGE("Can not create a codec for Gainmap.");
- return false;
- }
SkColorType decodeColorType = kN32_SkColorType;
if (codec->getInfo().colorType() == kGray_8_SkColorType) {
decodeColorType = kGray_8_SkColorType;
@@ -613,15 +607,15 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
bool hasGainmap = false;
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream = nullptr;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
sp<uirenderer::Gainmap> gainmap = nullptr;
if (result == SkCodec::kSuccess) {
- hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+ hasGainmap = codec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec);
}
if (hasGainmap) {
hasGainmap =
- decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+ decodeGainmap(std::move(gainmapCodec), gainmapInfo, &gainmap, sampleSize, scale);
}
if (!isMutable && javaBitmap == NULL) {
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 6a65b8273194..f7e8e073a272 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -48,25 +48,14 @@ public:
}
SkGainmapInfo gainmapInfo;
- std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<SkAndroidCodec> gainmapCodec;
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
- if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
- sk_sp<SkData> data = nullptr;
- if (gainmapStream->getMemoryBase()) {
- // It is safe to make without copy because we'll hold onto the stream.
- data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(),
- gainmapStream->getLength());
- } else {
- data = SkCopyStreamToData(gainmapStream.get());
- // We don't need to hold the stream anymore
- gainmapStream = nullptr;
- }
- gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data));
+ if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) {
+ gainmapBRD = nullptr;
}
- return std::unique_ptr<BitmapRegionDecoderWrapper>(
- new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD),
- gainmapInfo, std::move(gainmapStream)));
+ return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper(
+ std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo));
}
SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
@@ -191,16 +180,14 @@ public:
private:
BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
- SkGainmapInfo info, std::unique_ptr<SkStream> stream)
+ SkGainmapInfo info)
: mMainImageBRD(std::move(mainImageBRD))
, mGainmapBRD(std::move(gainmapBRD))
- , mGainmapInfo(info)
- , mGainmapStream(std::move(stream)) {}
+ , mGainmapInfo(info) {}
std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
SkGainmapInfo mGainmapInfo;
- std::unique_ptr<SkStream> mGainmapStream;
};
} // namespace android
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 286f06a6bad8..da237928e5e1 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -1127,6 +1127,36 @@ namespace PaintGlue {
return leftMinikinPaint == rightMinikinPaint;
}
+ struct VariationBuilder {
+ std::vector<minikin::FontVariation> varSettings;
+ };
+
+ static jlong createFontVariationBuilder(CRITICAL_JNI_PARAMS_COMMA jint size) {
+ VariationBuilder* builder = new VariationBuilder();
+ builder->varSettings.reserve(size);
+ return reinterpret_cast<jlong>(builder);
+ }
+
+ static void addFontVariationToBuilder(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jint tag,
+ jfloat value) {
+ VariationBuilder* builder = reinterpret_cast<VariationBuilder*>(builderPtr);
+ builder->varSettings.emplace_back(static_cast<minikin::AxisTag>(tag), value);
+ }
+
+ static void setFontVariationOverride(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle,
+ jlong builderPtr) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (builderPtr == 0) {
+ paint->setVariationOverride(minikin::VariationSettings());
+ return;
+ }
+
+ VariationBuilder* builder = reinterpret_cast<VariationBuilder*>(builderPtr);
+ paint->setVariationOverride(
+ minikin::VariationSettings(builder->varSettings, false /* sorted */));
+ delete builder;
+ }
+
}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
@@ -1235,6 +1265,9 @@ static const JNINativeMethod methods[] = {
{"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer},
{"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
+ {"nCreateFontVariationBuilder", "(I)J", (void*)PaintGlue::createFontVariationBuilder},
+ {"nAddFontVariationToBuilder", "(JIF)V", (void*)PaintGlue::addFontVariationToBuilder},
+ {"nSetFontVariationOverride", "(JJ)V", (void*)PaintGlue::setFontVariationOverride},
};
int register_android_graphics_Paint(JNIEnv* env) {
diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h
index 78db54acc9e5..91db134af18f 100644
--- a/libs/hwui/jni/graphics_jni_helpers.h
+++ b/libs/hwui/jni/graphics_jni_helpers.h
@@ -80,9 +80,52 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
return static_cast<T>(res);
}
+// Inline variable that specifies the method binding format.
+// The expected format is 'XX${method}XX', where ${method} represents the original method name.
+// This variable is shared across all translation units. This is treated as a global variable as
+// per C++ 17.
+inline std::string jniMethodFormat;
+
+inline static void setJniMethodFormat(std::string value) {
+ jniMethodFormat = value;
+}
+
+// Register the native methods, potenially applying the jniMethodFormat if it has been set.
+static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods,
+ int numMethods) {
+ if (jniMethodFormat.empty()) {
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ }
+
+ // Make a copy of gMethods with reformatted method names.
+ JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods];
+ LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods");
+
+ size_t methodNamePos = jniMethodFormat.find("${method}");
+ LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos,
+ "Invalid jniMethodFormat: could not find '${method}' in pattern");
+
+ for (int i = 0; i < numMethods; i++) {
+ modifiedMethods[i] = gMethods[i];
+ std::string modifiedName = jniMethodFormat;
+ modifiedName.replace(methodNamePos, 9, gMethods[i].name);
+ char* modifiedNameChars = new char[modifiedName.length() + 1];
+ LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name");
+ std::strcpy(modifiedNameChars, modifiedName.c_str());
+ modifiedMethods[i].name = modifiedNameChars;
+ }
+ int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods);
+ for (int i = 0; i < numMethods; i++) {
+ delete[] modifiedMethods[i].name;
+ }
+ delete[] modifiedMethods;
+ return res;
+}
+
static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
- int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
return res;
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index f0ab6ecc5cda..0f24654879cd 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -1089,7 +1089,24 @@ public class RingtoneManager {
defaultRingtoneUri = ContentProvider.getUriWithoutUserId(defaultRingtoneUri);
if (defaultRingtoneUri == null) {
return -1;
- } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
+ }
+
+ if (Flags.enableRingtoneHapticsCustomization()
+ && Utils.hasVibration(defaultRingtoneUri)) {
+ // skip to check TYPE_ALARM because the customized haptic hasn't enabled in alarm
+ if (defaultRingtoneUri.toString()
+ .contains(Settings.System.DEFAULT_RINGTONE_URI.toString())) {
+ return TYPE_RINGTONE;
+ } else if (defaultRingtoneUri.toString()
+ .contains(Settings.System.DEFAULT_NOTIFICATION_URI.toString())) {
+ return TYPE_NOTIFICATION;
+ } else if (defaultRingtoneUri.toString()
+ .contains(Settings.System.DEFAULT_ALARM_ALERT_URI.toString())) {
+ return TYPE_ALARM;
+ }
+ }
+
+ if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
return TYPE_RINGTONE;
} else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) {
return TYPE_NOTIFICATION;
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 9899e4ec388d..83a4dd5a682a 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -22,7 +22,6 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -57,8 +56,6 @@ public final class RoutingSessionInfo implements Parcelable {
}
};
- private static final String TAG = "RoutingSessionInfo";
-
private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
private static final String KEY_VOLUME_HANDLING = "volumeHandling";
@@ -142,15 +139,7 @@ public final class RoutingSessionInfo implements Parcelable {
mVolume = builder.mVolume;
mIsSystemSession = builder.mIsSystemSession;
-
- boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
- mVolumeHandling =
- defineVolumeHandling(
- mIsSystemSession,
- builder.mVolumeHandling,
- mSelectedRoutes,
- volumeAdjustmentForRemoteGroupSessions);
+ mVolumeHandling = builder.mVolumeHandling;
mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
mTransferReason = builder.mTransferReason;
@@ -207,20 +196,6 @@ public final class RoutingSessionInfo implements Parcelable {
return controlHints;
}
- @MediaRoute2Info.PlaybackVolume
- private static int defineVolumeHandling(
- boolean isSystemSession,
- @MediaRoute2Info.PlaybackVolume int volumeHandling,
- List<String> selectedRoutes,
- boolean volumeAdjustmentForRemoteGroupSessions) {
- if (!isSystemSession
- && !volumeAdjustmentForRemoteGroupSessions
- && selectedRoutes.size() > 1) {
- return MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
- }
- return volumeHandling;
- }
-
@NonNull
private static String ensureString(@Nullable String str) {
return str != null ? str : "";
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
index 3955ff068d94..5f5058d79545 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -18,8 +18,6 @@ package com.android.mediaroutertest;
import static com.google.common.truth.Truth.assertThat;
-import android.content.res.Resources;
-import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -95,24 +93,4 @@ public class RoutingSessionInfoTest {
assertThat(sessionInfoWithProviderId2.getTransferableRoutes())
.isEqualTo(sessionInfoWithProviderId.getTransferableRoutes());
}
-
- @Test
- public void testGetVolumeHandlingGroupSession() {
- RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
- TEST_ID, TEST_CLIENT_PACKAGE_NAME)
- .setName(TEST_NAME)
- .addSelectedRoute(TEST_ROUTE_ID_0)
- .addSelectedRoute(TEST_ROUTE_ID_2)
- .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .build();
-
- boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-
- int expectedResult = volumeAdjustmentForRemoteGroupSessions
- ? MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE :
- MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
-
- assertThat(sessionInfo.getVolumeHandling()).isEqualTo(expectedResult);
- }
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index e7cb76c370fd..96b7c1339190 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -223,6 +223,7 @@ package android.nfc.cardemulation {
field public static final String CATEGORY_PAYMENT = "payment";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3; // 0x3
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2; // 0x2
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 94231b0facdb..4428adee818d 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -58,12 +58,16 @@ package android.nfc {
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
@@ -90,7 +94,11 @@ package android.nfc {
method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableFinished(int);
method public void onEnableStarted();
+ method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
method public void onHceEventReceived(int);
+ method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
+ method public void onLaunchHceTapAgainDialog(@NonNull android.nfc.cardemulation.ApduServiceInfo, @NonNull String);
+ method public void onNdefMessage(@NonNull android.nfc.Tag, @NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onReaderOptionChanged(boolean);
method public void onRfDiscoveryStarted(boolean);
@@ -101,6 +109,12 @@ package android.nfc {
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
+ @FlaggedApi("android.nfc.nfc_oem_extension") public class RoutingStatus {
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultIsoDepRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultOffHostRoute();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultRoute();
+ }
+
}
package android.nfc.cardemulation {
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 19b9e0f0b515..1eae3c6f30f1 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -51,4 +51,8 @@ interface INfcCardEmulation
void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
void recoverRoutingTable(int userHandle);
boolean isEuiccSupported();
+ void setAutoChangeStatus(boolean state);
+ boolean isAutoChangeEnabled();
+ List<String> getRoutingStatus();
+ void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech);
}
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index e49ef7e80705..48c7ee659266 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -15,9 +15,14 @@
*/
package android.nfc;
+import android.content.ComponentName;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.NdefMessage;
import android.nfc.Tag;
import android.os.ResultReceiver;
+import java.util.List;
+
/**
* @hide
*/
@@ -41,4 +46,8 @@ interface INfcOemExtensionCallback {
void onCardEmulationActivated(boolean isActivated);
void onRfFieldActivated(boolean isActivated);
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+ void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
+ void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
+ void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
+ void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 6d5c069bca3a..fb63b5c03d00 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -16,6 +16,12 @@
package android.nfc;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+import static android.nfc.cardemulation.CardEmulation.routeIntToString;
+
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -23,8 +29,14 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
@@ -306,6 +318,60 @@ public final class NfcOemExtension {
* @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle.
*/
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+
+ /**
+ * Gets the intent to find the OEM package in the OEM App market. If the consumer returns
+ * {@code null} or a timeout occurs, the intent from the first available package will be
+ * used instead.
+ *
+ * @param packages the OEM packages name stored in the tag
+ * @param intentConsumer The {@link Consumer} to be completed.
+ * The {@link Consumer#accept(Object)} should be called with
+ * the Intent required.
+ *
+ */
+ void onGetOemAppSearchIntent(@NonNull List<String> packages,
+ @NonNull Consumer<Intent> intentConsumer);
+
+ /**
+ * Checks if the NDEF message contains any specific OEM package executable content
+ *
+ * @param tag the {@link android.nfc.Tag Tag}
+ * @param message NDEF Message to read from tag
+ * @param hasOemExecutableContent The {@link Consumer} to be completed. If there is
+ * OEM package executable content, the
+ * {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}, otherwise call with
+ * {@link Boolean#FALSE}.
+ */
+ void onNdefMessage(@NonNull Tag tag, @NonNull NdefMessage message,
+ @NonNull Consumer<Boolean> hasOemExecutableContent);
+
+ /**
+ * Callback to indicate the app chooser activity should be launched for handling CE
+ * transaction. This is invoked for example when there are more than 1 app installed that
+ * can handle the HCE transaction. OEMs can launch the Activity based
+ * on their requirement.
+ *
+ * @param selectedAid the selected AID from APDU
+ * @param services {@link ApduServiceInfo} of the service triggering the activity
+ * @param failedComponent the component failed to be resolved
+ * @param category the category of the service
+ */
+ void onLaunchHceAppChooserActivity(@NonNull String selectedAid,
+ @NonNull List<ApduServiceInfo> services,
+ @NonNull ComponentName failedComponent,
+ @NonNull String category);
+
+ /**
+ * Callback to indicate tap again dialog should be launched for handling HCE transaction.
+ * This is invoked for example when a CE service needs the device to unlocked before
+ * handling the transaction. OEMs can launch the Activity based on their requirement.
+ *
+ * @param service {@link ApduServiceInfo} of the service triggering the dialog
+ * @param category the category of the service
+ */
+ void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category);
}
@@ -523,6 +589,85 @@ public final class NfcOemExtension {
NfcAdapter.callService(() -> NfcAdapter.sService.resumePolling());
}
+ /**
+ * Set whether to enable auto routing change or not (enabled by default).
+ * If disabled, routing targets are limited to a single off-host destination.
+ *
+ * @param state status of auto routing change, true if enable, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setAutoChangeEnabled(boolean state) {
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.setAutoChangeStatus(state));
+ }
+
+ /**
+ * Check if auto routing change is enabled or not.
+ *
+ * @return true if enabled, otherwise false
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isAutoChangeEnabled() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.isAutoChangeEnabled(), false);
+ }
+
+ /**
+ * Get current routing status
+ *
+ * @return {@link RoutingStatus} indicating the default route, default ISO-DEP
+ * route and default off-host route.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public RoutingStatus getRoutingStatus() {
+ List<String> status = NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sCardEmulationService.getRoutingStatus(), new ArrayList<>());
+ return new RoutingStatus(routeStringToInt(status.get(0)),
+ routeStringToInt(status.get(1)),
+ routeStringToInt(status.get(2)));
+ }
+
+ /**
+ * Overwrites NFC controller routing table, which includes Protocol Route, Technology Route,
+ * and Empty AID Route.
+ *
+ * The parameter set to
+ * {@link ProtocolAndTechnologyRoute#PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. At least one route should be overridden
+ * when calling this API, otherwise throw {@link IllegalArgumentException}.
+ *
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ * @param emptyAid Zero-length AID route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public void overwriteRoutingTable(
+ @CardEmulation.ProtocolAndTechnologyRoute int protocol,
+ @CardEmulation.ProtocolAndTechnologyRoute int technology,
+ @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) {
+
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
+ String emptyAidRoute = routeIntToString(emptyAid);
+
+ NfcAdapter.callService(() ->
+ NfcAdapter.sCardEmulationService.overwriteRoutingTable(
+ mContext.getUser().getIdentifier(),
+ emptyAidRoute,
+ protocolRoute,
+ technologyRoute
+ ));
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
@@ -562,25 +707,25 @@ public final class NfcOemExtension {
public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onApplyRouting, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onApplyRouting, ex));
}
@Override
public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onNdefRead, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onNdefRead, ex));
}
@Override
public void onEnable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isAllowed), cb::onEnable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onEnable, ex));
}
@Override
public void onDisable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isAllowed), cb::onDisable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onDisable, ex));
}
@Override
public void onBootStarted() throws RemoteException {
@@ -616,7 +761,7 @@ public final class NfcOemExtension {
public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper(isSkipped), cb::onTagDispatch, ex));
+ new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex));
}
@Override
public void onRoutingChanged() throws RemoteException {
@@ -635,6 +780,59 @@ public final class NfcOemExtension {
handleVoidCallback(enabled, cb::onReaderOptionChanged, ex));
}
+ @Override
+ public void onGetOemAppSearchIntent(List<String> packages, ResultReceiver intentConsumer)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(packages, new ReceiverWrapper<>(intentConsumer),
+ cb::onGetOemAppSearchIntent, ex));
+ }
+
+ @Override
+ public void onNdefMessage(Tag tag, NdefMessage message,
+ ResultReceiver hasOemExecutableContent) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) -> {
+ synchronized (mLock) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ex.execute(() -> cb.onNdefMessage(
+ tag, message, new ReceiverWrapper<>(hasOemExecutableContent)));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onLaunchHceAppChooserActivity(String selectedAid,
+ List<ApduServiceInfo> services,
+ ComponentName failedComponent, String category)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) -> {
+ synchronized (mLock) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ex.execute(() -> cb.onLaunchHceAppChooserActivity(
+ selectedAid, services, failedComponent, category));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onLaunchHceTapAgainActivity(ApduServiceInfo service, String category)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(service, category, cb::onLaunchHceTapAgainDialog, ex));
+ }
+
private <T> void handleVoidCallback(
T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
@@ -718,7 +916,16 @@ public final class NfcOemExtension {
}
}
- private class ReceiverWrapper implements Consumer<Boolean> {
+ private @CardEmulation.ProtocolAndTechnologyRoute int routeStringToInt(String route) {
+ return switch (route) {
+ case "DH" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_DH;
+ case "eSE" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE;
+ case "SIM" -> PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC;
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
+
+ private class ReceiverWrapper<T> implements Consumer<T> {
private final ResultReceiver mResultReceiver;
ReceiverWrapper(ResultReceiver resultReceiver) {
@@ -726,12 +933,19 @@ public final class NfcOemExtension {
}
@Override
- public void accept(Boolean result) {
- mResultReceiver.send(result ? 1 : 0, null);
+ public void accept(T result) {
+ if (result instanceof Boolean) {
+ mResultReceiver.send((Boolean) result ? 1 : 0, null);
+ } else if (result instanceof Intent) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("intent", (Intent) result);
+ mResultReceiver.send(0, bundle);
+ }
+
}
@Override
- public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+ public Consumer<T> andThen(Consumer<? super T> after) {
return Consumer.super.andThen(after);
}
}
diff --git a/nfc/java/android/nfc/RoutingStatus.java b/nfc/java/android/nfc/RoutingStatus.java
new file mode 100644
index 000000000000..4a1b1f3cecbc
--- /dev/null
+++ b/nfc/java/android/nfc/RoutingStatus.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
+
+/**
+ * A class indicating default route, ISO-DEP route and off-host route.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+public class RoutingStatus {
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute;
+ private final @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute;
+
+ RoutingStatus(@CardEmulation.ProtocolAndTechnologyRoute int mDefaultRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultIsoDepRoute,
+ @CardEmulation.ProtocolAndTechnologyRoute int mDefaultOffHostRoute) {
+ this.mDefaultRoute = mDefaultRoute;
+ this.mDefaultIsoDepRoute = mDefaultIsoDepRoute;
+ this.mDefaultOffHostRoute = mDefaultOffHostRoute;
+ }
+
+ /**
+ * Getter of the default route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultRoute() {
+ return mDefaultRoute;
+ }
+
+ /**
+ * Getter of the default ISO-DEP route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultIsoDepRoute() {
+ return mDefaultIsoDepRoute;
+ }
+
+ /**
+ * Getter of the default off-host route.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getDefaultOffHostRoute() {
+ return mDefaultOffHostRoute;
+ }
+
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 4be082ccc02f..d8f04c50b695 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,12 @@ public final class CardEmulation {
public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2;
/**
+ * Route to the default value in config file.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3;
+
+ /**
* Route unset.
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
@@ -895,45 +901,47 @@ public final class CardEmulation {
PROTOCOL_AND_TECHNOLOGY_ROUTE_DH,
PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE,
PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC,
- PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolAndTechnologyRoute {}
- /**
- * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
- * while this Activity is in the foreground.
- *
- * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
- * can be used to keep current values for that entry. Either
- * Protocol Route or Technology Route should be override when calling this API, otherwise
- * throw {@link IllegalArgumentException}.
- * <p>
- * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
- * <pre>
- * protected void onResume() {
- * mNfcAdapter.overrideRoutingTable(
- * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE}, null);
- * }</pre>
- * </p>
- * Also activities must call {@link #recoverRoutingTable(Activity)}
- * when it goes to the background. Only the package of the
- * currently preferred service (the service set as preferred by the current foreground
- * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
- * otherwise a call to this method will fail and throw {@link SecurityException}.
- * @param activity The Activity that requests NFC controller routing table to be changed.
- * @param protocol ISO-DEP route destination, where the possible inputs are defined
- * in {@link ProtocolAndTechnologyRoute}.
- * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
- * are defined in {@link ProtocolAndTechnologyRoute}
- * @throws SecurityException if the caller is not the preferred NFC service
- * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
- * foreground.
- * <p>
- * This is a high risk API and only included to support mainline effort
- * @hide
- */
+ /**
+ * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+ * while this Activity is in the foreground.
+ *
+ * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. Either
+ * Protocol Route or Technology Route should be override when calling this API, otherwise
+ * throw {@link IllegalArgumentException}.
+ * <p>
+ * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter.overrideRoutingTable(
+ * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE},
+ * {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET});
+ * }</pre>
+ * </p>
+ * Also activities must call {@link #recoverRoutingTable(Activity)}
+ * when it goes to the background. Only the package of the
+ * currently preferred service (the service set as preferred by the current foreground
+ * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
+ * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
+ * otherwise a call to this method will fail and throw {@link SecurityException}.
+ * @param activity The Activity that requests NFC controller routing table to be changed.
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined
+ * in {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in {@link ProtocolAndTechnologyRoute}
+ * @throws SecurityException if the caller is not the preferred NFC service
+ * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
+ * foreground.
+ * <p>
+ * This is a high risk API and only included to support mainline effort
+ * @hide
+ */
@SystemApi
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void overrideRoutingTable(
@@ -942,26 +950,14 @@ public final class CardEmulation {
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- String protocolRoute = switch (protocol) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
- String technologyRoute = switch (technology) {
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
- case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
- default -> throw new IllegalStateException("Unexpected value: " + protocol);
- };
+ String protocolRoute = routeIntToString(protocol);
+ String technologyRoute = routeIntToString(technology);
callService(() ->
sService.overrideRoutingTable(
- mContext.getUser().getIdentifier(),
- protocolRoute,
- technologyRoute,
- mContext.getPackageName()));
+ mContext.getUser().getIdentifier(),
+ protocolRoute,
+ technologyRoute,
+ mContext.getPackageName()));
}
/**
@@ -1068,4 +1064,16 @@ public final class CardEmulation {
}
return defaultReturn;
}
+
+ /** @hide */
+ public static String routeIntToString(@ProtocolAndTechnologyRoute int route) {
+ return switch (route) {
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "eSE";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "SIM";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT -> "default";
+ default -> throw new IllegalStateException("Unexpected value: " + route);
+ };
+ }
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index cc9a97cd52c9..6a7e6939e773 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -157,3 +157,11 @@ flag {
description: "Enable EUICC card emulation"
bug: "321314635"
}
+
+flag {
+ name: "nfc_state_change_security_log_event_enabled"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enabling security log for nfc state change"
+ bug: "319934052"
+}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index a57d6eb11927..b266912ca156 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -50,13 +50,13 @@
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
+ <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
<!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on the <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps and system features from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -80,13 +80,13 @@
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
+ <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
<!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps and system features to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
+ <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>, including audio, photos, payment info, passwords, and messages.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features between your devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
<!-- ================= null profile ================= -->
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
deleted file mode 100644
index a1761e55f1e0..000000000000
--- a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <declare-styleable name="ButtonPreference">
- <attr name="buttonType" format="enum">
- <enum name="filled" value="0"/>
- <enum name="tonal" value="1"/>
- <enum name="outline" value="2"/>
- </attr>
- <attr name="buttonSize" format="enum">
- <enum name="normal" value="0"/>
- <enum name="large" value="1"/>
- <enum name="extra" value="2"/>
- </attr>
- </declare-styleable>
-</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
index 9c1e503f1372..970eeb2bfa72 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -18,12 +18,12 @@
<resources>
<declare-styleable name="ButtonPreference">
<attr name="android:gravity" />
- <attr name="buttonType" format="enum">
+ <attr name="buttonPreferenceType" format="enum">
<enum name="filled" value="0"/>
<enum name="tonal" value="1"/>
<enum name="outline" value="2"/>
</attr>
- <attr name="buttonSize" format="enum">
+ <attr name="buttonPreferenceSize" format="enum">
<enum name="normal" value="0"/>
<enum name="large" value="1"/>
<enum name="extra" value="2"/>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 0041eb2c7072..979ff96be3f7 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -137,8 +137,8 @@ public class ButtonPreference extends Preference {
mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
if (SettingsThemeHelper.isExpressiveTheme(context)) {
- int type = a.getInt(R.styleable.ButtonPreference_buttonType, 0);
- int size = a.getInt(R.styleable.ButtonPreference_buttonSize, 0);
+ int type = a.getInt(R.styleable.ButtonPreference_buttonPreferenceType, 0);
+ int size = a.getInt(R.styleable.ButtonPreference_buttonPreferenceSize, 0);
resId = ButtonStyle.getLayoutId(type, size);
}
a.recycle();
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8b9ec389e3fd..739c7d6bcfca 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1651,6 +1651,9 @@
<!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_usb_device_mic_name">USB microphone</string>
+ <!-- Name of the bluetooth audio device mic. [CHAR LIMIT=50] -->
+ <string name="media_transfer_bt_device_mic_name">BT microphone</string>
+
<!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
<string name="wifi_hotspot_switch_on_text">On</string>
<!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d56c77b097b..744e97e53fef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -30,9 +30,10 @@ import android.hardware.usb.flags.Flags;
import android.icu.text.NumberFormat;
import android.location.LocationManager;
import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
-import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.os.BatteryManager;
import android.os.Build;
@@ -737,14 +738,9 @@ public class Utils {
* @param networkCapabilities NetworkCapabilities of the network.
*/
@Nullable
- public static WifiInfo tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.getTransportInfo() == null
- || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
- return null;
- }
- VcnTransportInfo vcnTransportInfo =
- (VcnTransportInfo) networkCapabilities.getTransportInfo();
- return vcnTransportInfo.getWifiInfo();
+ public static WifiInfo tryGetWifiInfoForVcn(
+ ConnectivityManager connectivityMgr, NetworkCapabilities networkCapabilities) {
+ return VcnUtils.getWifiInfoFromVcnCaps(connectivityMgr, networkCapabilities);
}
/** Whether there is any incompatible chargers in the current UsbPort? */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index e44a13491758..1d17b004fb6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -90,7 +91,8 @@ public class InputMediaDevice extends MediaDevice {
TYPE_WIRED_HEADSET,
TYPE_USB_DEVICE,
TYPE_USB_HEADSET,
- TYPE_USB_ACCESSORY ->
+ TYPE_USB_ACCESSORY,
+ TYPE_BLUETOOTH_SCO ->
true;
default -> false;
};
@@ -103,6 +105,8 @@ public class InputMediaDevice extends MediaDevice {
R.string.media_transfer_wired_device_mic_name);
case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
R.string.media_transfer_usb_device_mic_name);
+ case TYPE_BLUETOOTH_SCO -> mContext.getString(
+ R.string.media_transfer_bt_device_mic_name);
default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
};
return name.toString();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 015356e013b7..cea3d17f116d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -30,6 +30,7 @@ import android.net.NetworkScoreManager;
import android.net.ScoredNetwork;
import android.net.TransportInfo;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.VcnUtils;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -394,10 +395,7 @@ public class WifiStatusTracker {
TransportInfo transportInfo = networkCapabilities.getTransportInfo();
if (transportInfo instanceof VcnTransportInfo) {
- // This VcnTransportInfo logic is copied from
- // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
- // re-used because it makes the logic here clearer.
- return ((VcnTransportInfo) transportInfo).getWifiInfo();
+ return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities);
} else if (transportInfo instanceof WifiInfo) {
return (WifiInfo) transportInfo;
} else {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 2f0aa1cf0cb9..30e4637f59ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -37,6 +37,7 @@ public class InputMediaDeviceTest {
private final int BUILTIN_MIC_ID = 1;
private final int WIRED_HEADSET_ID = 2;
private final int USB_HEADSET_ID = 3;
+ private final int BT_HEADSET_ID = 4;
private final int MAX_VOLUME = 1;
private final int CURRENT_VOLUME = 0;
private final boolean IS_VOLUME_FIXED = true;
@@ -108,4 +109,19 @@ public class InputMediaDeviceTest {
assertThat(usbMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
}
+
+ @Test
+ public void getName_returnCorrectName_btHeadset() {
+ InputMediaDevice btMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BT_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED);
+ assertThat(btMediaDevice).isNotNull();
+ assertThat(btMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5e31da411e49..4dc84246afc0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -168,10 +168,6 @@ public class SecureSettings {
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
Settings.Secure.ZEN_DURATION,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
Settings.Secure.CHARGING_SOUNDS_ENABLED,
Settings.Secure.CHARGING_VIBRATION_ENABLED,
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c04225d9dece..8f58e8cd1973 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -186,7 +186,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(
Global.STEM_PRIMARY_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(
- Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+ Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 2));
VALIDATORS.put(
Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b3f73749f393..688676dc4072 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -247,10 +247,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3c24f5c56603..2034f36c558b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2734,18 +2734,6 @@ class SettingsProtoDumpUtil {
Settings.Secure.ZEN_DURATION,
SecureSettingsProto.Zen.DURATION);
dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
- SecureSettingsProto.Zen.SHOW_ZEN_UPGRADE_NOTIFICATION);
- dumpSetting(s, p,
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
- SecureSettingsProto.Zen.SHOW_ZEN_SETTINGS_SUGGESTION);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_UPDATED,
- SecureSettingsProto.Zen.SETTINGS_UPDATED);
- dumpSetting(s, p,
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
- SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
- dumpSetting(s, p,
Settings.Secure.CHARGE_OPTIMIZATION_MODE,
SecureSettingsProto.CHARGE_OPTIMIZATION_MODE);
p.end(zenToken);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 749ad0a993b3..a8af43f5cb11 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4771,9 +4771,9 @@ public class SettingsProvider extends ContentProvider {
}
if (currentVersion == 169) {
- // Version 169: Set the default value for Secure Settings ZEN_DURATION,
- // SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
- // ZEN_SETTINGS_SUGGESTION_VIEWED
+ // Version 169: Set the default value for Secure Settings ZEN_DURATION.
+ // Also used to update SHOW_ZEN_SETTINGS_SUGGESTION, ZEN_SETTINGS_UPDATE and
+ // ZEN_SETTINGS_SUGGESTION_VIEWED, but those properties are gone now.
final SettingsState globalSettings = getGlobalSettingsLocked();
final Setting globalZenDuration = globalSettings.getSettingLocked(
@@ -4801,33 +4801,6 @@ public class SettingsProvider extends ContentProvider {
SettingsState.SYSTEM_PACKAGE_NAME);
}
- // SHOW_ZEN_SETTINGS_SUGGESTION
- final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
- if (currentShowZenSettingSuggestion.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_UPDATED
- final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_UPDATED);
- if (currentUpdatedSetting.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_UPDATED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- // ZEN_SETTINGS_SUGGESTION_VIEWED
- final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
- if (currentSettingSuggestionViewed.isNull()) {
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
currentVersion = 170;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3c634f067a0d..011ffbc97d19 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -610,7 +610,7 @@ final class SettingsState {
flag.getPackageName(),
flag.getFlagName(),
flag.getServerFlagValue(),
- false);
+ StorageRequestMessage.SERVER_ON_REBOOT);
}
if (flag.getHasLocalOverride()) {
@@ -619,7 +619,7 @@ final class SettingsState {
flag.getPackageName(),
flag.getFlagName(),
flag.getLocalFlagValue(),
- true);
+ StorageRequestMessage.LOCAL_ON_REBOOT);
}
}
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index dccc2d398f12..541a29465b9a 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -32,7 +32,7 @@
<option name="package" value="com.android.providers.setting.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
</test>
</configuration>
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index e4898daf3cbf..e86e72712b48 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -31,10 +31,10 @@ import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile;
import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.users.UserType;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d98b2da5a811..c6238e848070 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -637,6 +637,13 @@ flag {
}
flag {
+ name: "screenshot_policy_split_and_desktop_mode"
+ namespace: "systemui"
+ description: "Improves screenshot policy handling for split screen and desktop mode."
+ bug: "365597999"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1074,6 +1081,16 @@ flag {
}
flag {
+ name: "dream_overlay_updated_font"
+ namespace: "systemui"
+ description: "Flag to enable updated font settings for dream overlay"
+ bug: "349656117"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1384,10 +1401,13 @@ flag {
}
flag {
- name: "compose_haptic_sliders"
+ name: "haptics_for_compose_sliders"
namespace: "systemui"
description: "Adding haptic component infrastructure to sliders in Compose."
bug: "341968766"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -1453,3 +1473,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "check_lockscreen_gone_transition"
+ namespace: "systemui"
+ description: "Run notification pipeline when the lockscreen is not in gone transition for avoiding janky frames during unlocking animation"
+ bug: "358301118"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index f5d01d70e077..907c39d842ce 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -944,9 +944,26 @@ private class AnimatedDialog(
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- startController.onTransitionAnimationEnd(isExpandingFullyAbove)
- endController.onTransitionAnimationEnd(isExpandingFullyAbove)
- onLaunchAnimationEnd()
+ // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+ // on a Choreographer animation tick. The following calls will move the animated
+ // content from the dialog overlay back to its original position, and this
+ // change must be reflected in the next frame given that we then sync the next
+ // frame of both the content and dialog ViewRoots. However, in case that content
+ // is rendered by Compose, whose compositions are also scheduled on a
+ // Choreographer frame, any state change made *right now* won't be reflected in
+ // the next frame given that a Choreographer frame can't schedule another and
+ // have it happen in the same frame. So we post the forwarded calls to
+ // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+ // that the move of the content back to its original window will be reflected in
+ // the next frame right after [onLaunchAnimationEnd] is called.
+ //
+ // TODO(b/330672236): Move this to TransitionAnimator.
+ dialog.context.mainExecutor.execute {
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+
+ onLaunchAnimationEnd()
+ }
}
override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 859fc4e09bb2..fc4cf1d1e21e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -379,26 +379,13 @@ class TransitionAnimator(
Log.d(TAG, "Animation ended")
}
- // onAnimationEnd is called at the end of the animation, on a Choreographer
- // animation tick. During dialog launches, the following calls will move the
- // animated content from the dialog overlay back to its original position, and
- // this change must be reflected in the next frame given that we then sync the
- // next frame of both the content and dialog ViewRoots. During SysUI activity
- // launches, we will instantly collapse the shade at the end of the transition.
- // However, if those are rendered by Compose, whose compositions are also
- // scheduled on a Choreographer frame, any state change made *right now* won't
- // be reflected in the next frame given that a Choreographer frame can't
- // schedule another and have it happen in the same frame. So we post the
- // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
- // leaving this Choreographer frame, ensuring that any state change applied by
- // onTransitionAnimationEnd() will be reflected in the same frame.
- mainExecutor.execute {
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
-
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
- }
+ // TODO(b/330672236): Post this to the main thread instead so that it does not
+ // flicker with Flexiglass enabled.
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
}
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
new file mode 100644
index 000000000000..3f2f84b95977
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import kotlin.math.roundToInt
+
+/** A component that can bounce in one dimension, for instance when it is tapped. */
+interface Bounceable {
+ val bounce: Dp
+}
+
+/**
+ * Bounce a composable in the given [orientation] when this [bounceable], the [previousBounceable]
+ * or [nextBounceable] is bouncing.
+ *
+ * Important: This modifier should be used on composables that have a fixed size in [orientation],
+ * i.e. they should be placed *after* modifiers like Modifier.fillMaxWidth() or Modifier.height().
+ *
+ * @param bounceable the [Bounceable] associated to the current composable that will make this
+ * composable size grow when bouncing.
+ * @param previousBounceable the [Bounceable] associated to the previous composable in [orientation]
+ * that will make this composable shrink when bouncing.
+ * @param nextBounceable the [Bounceable] associated to the next composable in [orientation] that
+ * will make this composable shrink when bouncing.
+ * @param orientation the orientation in which this bounceable should grow/shrink.
+ * @param bounceEnd whether this bounceable should bounce on the end (right in LTR layouts, left in
+ * RTL layouts) side. This can be used for grids for which the last item does not align perfectly
+ * with the end of the grid.
+ */
+fun Modifier.bounceable(
+ bounceable: Bounceable,
+ previousBounceable: Bounceable?,
+ nextBounceable: Bounceable?,
+ orientation: Orientation,
+ bounceEnd: Boolean = nextBounceable != null,
+): Modifier {
+ return layout { measurable, constraints ->
+ // The constraints in the orientation should be fixed, otherwise there is no way to know
+ // what the size of our child node will be without this animation code.
+ checkFixedSize(constraints, orientation)
+
+ var sizePrevious = 0f
+ var sizeNext = 0f
+
+ if (previousBounceable != null) {
+ sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
+ }
+
+ if (nextBounceable != null) {
+ sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
+ } else if (bounceEnd) {
+ sizeNext += bounceable.bounce.toPx()
+ }
+
+ when (orientation) {
+ Orientation.Horizontal -> {
+ val idleWidth = constraints.maxWidth
+ val animatedWidth = (idleWidth + sizePrevious + sizeNext).roundToInt()
+ val animatedConstraints =
+ constraints.copy(minWidth = animatedWidth, maxWidth = animatedWidth)
+
+ val placeable = measurable.measure(animatedConstraints)
+
+ // Important: we still place the element using the idle size coming from the
+ // constraints, otherwise the parent will automatically center this node given the
+ // size that it expects us to be. This allows us to then place the element where we
+ // want it to be.
+ layout(idleWidth, placeable.height) {
+ placeable.placeRelative(-sizePrevious.roundToInt(), 0)
+ }
+ }
+ Orientation.Vertical -> {
+ val idleHeight = constraints.maxHeight
+ val animatedHeight = (idleHeight + sizePrevious + sizeNext).roundToInt()
+ val animatedConstraints =
+ constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)
+
+ val placeable = measurable.measure(animatedConstraints)
+ layout(placeable.width, idleHeight) {
+ placeable.placeRelative(0, -sizePrevious.roundToInt())
+ }
+ }
+ }
+ }
+}
+
+private fun checkFixedSize(constraints: Constraints, orientation: Orientation) {
+ when (orientation) {
+ Orientation.Horizontal -> {
+ check(constraints.hasFixedWidth) {
+ "Modifier.bounceable() should receive a fixed width from its parent. Make sure " +
+ "that it is used *after* a fixed-width Modifier in the horizontal axis (like" +
+ " Modifier.fillMaxWidth() or Modifier.width())."
+ }
+ }
+ Orientation.Vertical -> {
+ check(constraints.hasFixedHeight) {
+ "Modifier.bounceable() should receive a fixed height from its parent. Make sure " +
+ "that it is used *after* a fixed-height Modifier in the vertical axis (like" +
+ " Modifier.fillMaxHeight() or Modifier.height())."
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
new file mode 100644
index 000000000000..335e9f8872a6
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/BounceableTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BounceableTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun bounceable_horizontal() {
+ var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+ rule.setContent {
+ Row(Modifier.size(100.dp, 50.dp)) {
+ repeat(bounceables.size) { i ->
+ Box(
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .bounceable(bounceables, i, orientation = Orientation.Horizontal)
+ )
+ }
+ }
+ }
+
+ // All bounceables have a width of (100dp / bounceables.size) = 25dp and height of 50dp.
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(25.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+ }
+
+ // If all bounceables have the same bounce, it's the same as if they didn't have any.
+ bounceables = List(4) { bounceable(10.dp) }
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(25.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp)
+ }
+
+ // Bounce the first and third one.
+ bounceables =
+ listOf(
+ bounceable(bounce = 5.dp),
+ bounceable(bounce = 0.dp),
+ bounceable(bounce = 10.dp),
+ bounceable(bounce = 0.dp),
+ )
+
+ // First one has a width of 25dp + 5dp, located in (0, 0).
+ rule
+ .onNodeWithTag(bounceableTag(0))
+ .assertWidthIsEqualTo(30.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Second one has a width of 25dp - 5dp - 10dp, located in (30, 0).
+ rule
+ .onNodeWithTag(bounceableTag(1))
+ .assertWidthIsEqualTo(10.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 0.dp)
+
+ // Third one has a width of 25 + 2 * 10dp, located in (40, 0).
+ rule
+ .onNodeWithTag(bounceableTag(2))
+ .assertWidthIsEqualTo(45.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 0.dp)
+
+ // First one has a width of 25dp - 10dp, located in (85, 0).
+ rule
+ .onNodeWithTag(bounceableTag(3))
+ .assertWidthIsEqualTo(15.dp)
+ .assertHeightIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(85.dp, 0.dp)
+ }
+
+ @Test
+ fun bounceable_vertical() {
+ var bounceables by mutableStateOf(List(4) { bounceable(0.dp) })
+
+ rule.setContent {
+ Column(Modifier.size(50.dp, 100.dp)) {
+ repeat(bounceables.size) { i ->
+ Box(
+ Modifier.weight(1f)
+ .fillMaxWidth()
+ .bounceable(bounceables, i, Orientation.Vertical)
+ )
+ }
+ }
+ }
+
+ // All bounceables have a height of (100dp / bounceables.size) = 25dp and width of 50dp.
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(25.dp)
+ .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+ }
+
+ // If all bounceables have the same bounce, it's the same as if they didn't have any.
+ bounceables = List(4) { bounceable(10.dp) }
+ repeat(bounceables.size) { i ->
+ rule
+ .onNodeWithTag(bounceableTag(i))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(25.dp)
+ .assertPositionInRootIsEqualTo(0.dp, i * 25.dp)
+ }
+
+ // Bounce the first and third one.
+ bounceables =
+ listOf(
+ bounceable(bounce = 5.dp),
+ bounceable(bounce = 0.dp),
+ bounceable(bounce = 10.dp),
+ bounceable(bounce = 0.dp),
+ )
+
+ // First one has a height of 25dp + 5dp, located in (0, 0).
+ rule
+ .onNodeWithTag(bounceableTag(0))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(30.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Second one has a height of 25dp - 5dp - 10dp, located in (0, 30).
+ rule
+ .onNodeWithTag(bounceableTag(1))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(10.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 30.dp)
+
+ // Third one has a height of 25 + 2 * 10dp, located in (0, 40).
+ rule
+ .onNodeWithTag(bounceableTag(2))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(45.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 40.dp)
+
+ // First one has a height of 25dp - 10dp, located in (0, 85).
+ rule
+ .onNodeWithTag(bounceableTag(3))
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(15.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 85.dp)
+ }
+
+ private fun bounceable(bounce: Dp): Bounceable {
+ return object : Bounceable {
+ override val bounce: Dp = bounce
+ }
+ }
+
+ private fun Modifier.bounceable(
+ bounceables: List<Bounceable>,
+ i: Int,
+ orientation: Orientation,
+ ): Modifier {
+ val previous = if (i > 0) bounceables[i - 1] else null
+ val next = if (i < bounceables.lastIndex) bounceables[i + 1] else null
+ return this.bounceable(bounceables[i], previous, next, orientation)
+ .testTag(bounceableTag(i))
+ }
+
+ private fun bounceableTag(i: Int) = "bounceable$i"
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
new file mode 100644
index 000000000000..2f8236969185
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/DreamSceneModule.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene
+
+import com.android.systemui.dream.ui.composable.DreamScene
+import com.android.systemui.scene.ui.composable.Scene
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface DreamSceneModule {
+ @Binds @IntoSet fun dreamScene(scene: DreamScene): Scene
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
new file mode 100644
index 000000000000..fda46b855a65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.anchoredDraggable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastIsFinite
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.DragHandle
+import com.android.systemui.communal.ui.viewmodel.ResizeInfo
+import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+
+@Composable
+private fun UpdateGridLayoutInfo(
+ viewModel: ResizeableItemFrameViewModel,
+ index: Int,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+) {
+ val density = LocalDensity.current
+ LaunchedEffect(
+ density,
+ viewModel,
+ index,
+ gridState,
+ minItemSpan,
+ gridContentPadding,
+ verticalArrangement,
+ ) {
+ val verticalItemSpacingPx = with(density) { verticalArrangement.spacing.toPx() }
+ val verticalContentPaddingPx =
+ with(density) {
+ (gridContentPadding.calculateTopPadding() +
+ gridContentPadding.calculateBottomPadding())
+ .toPx()
+ }
+
+ combine(
+ snapshotFlow { gridState.layoutInfo.maxSpan },
+ snapshotFlow { gridState.layoutInfo.viewportSize.height },
+ snapshotFlow {
+ gridState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
+ }
+ .filterNotNull(),
+ ::Triple,
+ )
+ .collectLatest { (maxItemSpan, viewportHeightPx, itemInfo) ->
+ viewModel.setGridLayoutInfo(
+ verticalItemSpacingPx,
+ verticalContentPaddingPx,
+ viewportHeightPx,
+ maxItemSpan,
+ minItemSpan,
+ itemInfo.row,
+ itemInfo.span,
+ )
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.DragHandle(
+ handle: DragHandle,
+ dragState: AnchoredDraggableState<Int>,
+ outlinePadding: Dp,
+ brush: Brush,
+ alpha: () -> Float,
+ modifier: Modifier = Modifier,
+) {
+ val directionalModifier = if (handle == DragHandle.TOP) -1 else 1
+ val alignment = if (handle == DragHandle.TOP) Alignment.TopCenter else Alignment.BottomCenter
+ Box(
+ modifier
+ .align(alignment)
+ .graphicsLayer {
+ translationY =
+ directionalModifier * (size.height / 2 + outlinePadding.toPx()) +
+ (dragState.offset.takeIf { it.fastIsFinite() } ?: 0f)
+ }
+ .anchoredDraggable(dragState, Orientation.Vertical)
+ ) {
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ if (dragState.anchors.size > 1) {
+ drawCircle(
+ brush = brush,
+ radius = outlinePadding.toPx(),
+ center = Offset(size.width / 2, size.height / 2),
+ alpha = alpha(),
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Draws a frame around the content with drag handles on the top and bottom of the content.
+ *
+ * @param index The index of this item in the [LazyGridState].
+ * @param gridState The [LazyGridState] for the grid containing this item.
+ * @param minItemSpan The minimum span that an item may occupy. Items are resized in multiples of
+ * this span.
+ * @param gridContentPadding The content padding used for the grid, needed for determining offsets.
+ * @param verticalArrangement The vertical arrangement of the grid items.
+ * @param modifier Optional modifier to apply to the frame.
+ * @param enabled Whether resizing is enabled.
+ * @param outlinePadding The padding to apply around the entire frame, in [Dp]
+ * @param outlineColor Optional color to make the outline around the content.
+ * @param cornerRadius Optional radius to give to the outline around the content.
+ * @param strokeWidth Optional stroke width to draw the outline with.
+ * @param alpha Optional function to provide an alpha value for the outline. Can be used to fade the
+ * outline in and out. This is wrapped in a function for performance, as the value is only
+ * accessed during the draw phase.
+ * @param onResize Optional callback which gets executed when the item is resized to a new span.
+ * @param content The content to draw inside the frame.
+ */
+@Composable
+fun ResizableItemFrame(
+ index: Int,
+ gridState: LazyGridState,
+ minItemSpan: Int,
+ gridContentPadding: PaddingValues,
+ verticalArrangement: Arrangement.Vertical,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ outlinePadding: Dp = 8.dp,
+ outlineColor: Color = LocalAndroidColorScheme.current.primary,
+ cornerRadius: Dp = 37.dp,
+ strokeWidth: Dp = 3.dp,
+ alpha: () -> Float = { 1f },
+ onResize: (info: ResizeInfo) -> Unit = {},
+ content: @Composable () -> Unit,
+) {
+ val brush = SolidColor(outlineColor)
+ val viewModel =
+ rememberViewModel(traceName = "ResizeableItemFrame.viewModel") {
+ ResizeableItemFrameViewModel()
+ }
+
+ val dragHandleHeight = verticalArrangement.spacing - outlinePadding * 2
+
+ // Draw content surrounded by drag handles at top and bottom. Allow drag handles
+ // to overlap content.
+ Box(modifier) {
+ content()
+
+ if (enabled) {
+ DragHandle(
+ handle = DragHandle.TOP,
+ dragState = viewModel.topDragState,
+ outlinePadding = outlinePadding,
+ brush = brush,
+ alpha = alpha,
+ modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+ )
+
+ DragHandle(
+ handle = DragHandle.BOTTOM,
+ dragState = viewModel.bottomDragState,
+ outlinePadding = outlinePadding,
+ brush = brush,
+ alpha = alpha,
+ modifier = Modifier.fillMaxWidth().height(dragHandleHeight),
+ )
+
+ // Draw outline around the element.
+ Canvas(modifier = Modifier.matchParentSize()) {
+ val paddingPx = outlinePadding.toPx()
+ val topOffset = viewModel.topDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+ val bottomOffset =
+ viewModel.bottomDragState.offset.takeIf { it.fastIsFinite() } ?: 0f
+ drawRoundRect(
+ brush,
+ alpha = alpha(),
+ topLeft = Offset(-paddingPx, topOffset + -paddingPx),
+ size =
+ Size(
+ width = size.width + paddingPx * 2,
+ height = -topOffset + bottomOffset + size.height + paddingPx * 2,
+ ),
+ cornerRadius = CornerRadius(cornerRadius.toPx()),
+ style = Stroke(width = strokeWidth.toPx()),
+ )
+ }
+
+ UpdateGridLayoutInfo(
+ viewModel,
+ index,
+ gridState,
+ minItemSpan,
+ gridContentPadding,
+ verticalArrangement,
+ )
+ LaunchedEffect(viewModel) { viewModel.resizeInfo.collectLatest(onResize) }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
new file mode 100644
index 000000000000..f4374c6c9487
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.dream.ui.composable
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.ui.viewmodel.DreamUserActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.Scene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** The dream scene shows when a dream activity is showing. */
+@SysUISingleton
+class DreamScene
+@Inject
+constructor(private val actionsViewModelFactory: DreamUserActionsViewModel.Factory) :
+ ExclusiveActivatable(), Scene {
+ override val key = Scenes.Dream
+
+ private val actionsViewModel: DreamUserActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
+ override suspend fun onActivated(): Nothing {
+ actionsViewModel.activate()
+ }
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier = modifier.fillMaxSize()) {
+ // Render a sleep emoji to make the scene appear visible.
+ Text(
+ modifier = Modifier.padding(16.dp).align(Alignment.BottomStart),
+ text = "\uD83D\uDCA4",
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2745f6ea6bb4..4c6834cf6bea 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -47,7 +47,6 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -60,7 +59,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
@@ -70,7 +68,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
@@ -82,6 +79,7 @@ import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
@@ -93,8 +91,9 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadi
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.session.ui.composable.rememberSession
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -112,18 +111,16 @@ object Notifications {
val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
val HeadsUpNotificationPlaceholder =
ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker)
- val ShelfSpace = ElementKey("ShelfSpace")
val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline")
}
-
- // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
- // at its maximum, given they are at their minimum value at expansion = 0f.
- object TransitionThresholds {
- const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
- const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
- }
}
+private val notificationsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
+
+private val quickSettingsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.QuickSettingsShade else Scenes.QuickSettings
+
/**
* Adds the space where heads up notifications can appear in the scene. This should generally be the
* entire size of the scene.
@@ -146,7 +143,7 @@ fun SceneScope.HeadsUpNotificationSpace(
// This element is sometimes opted out of the shared element system, so there
// can be multiple instances of it during a transition. Thus we need to
// determine which instance should feed its bounds to NSSL to avoid providing
- // conflicting values
+ // conflicting values.
val useBounds = useHunBounds()
if (useBounds) {
val positionInWindow = coordinates.positionInWindow()
@@ -157,8 +154,8 @@ fun SceneScope.HeadsUpNotificationSpace(
" bounds=$boundsInWindow"
}
// Note: boundsInWindow doesn't scroll off the screen, so use
- // positionInWindow
- // for top bound, which can scroll off screen while snoozing
+ // positionInWindow for top bound, which can scroll off screen while
+ // snoozing.
stackScrollView.setHeadsUpTop(positionInWindow.y)
stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
@@ -285,7 +282,8 @@ fun SceneScope.NotificationScrollingStack(
shouldFillMaxSize: Boolean = true,
shouldReserveSpaceForNavBar: Boolean = true,
shouldIncludeHeadsUpSpace: Boolean = true,
- shadeMode: ShadeMode,
+ shouldShowScrim: Boolean = true,
+ supportNestedScrolling: Boolean,
onEmptySpaceClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
@@ -293,6 +291,7 @@ fun SceneScope.NotificationScrollingStack(
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
+ val scrimBackgroundColor = MaterialTheme.colorScheme.surface
val scrollState =
shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
ScrollState(initial = 0)
@@ -427,8 +426,14 @@ fun SceneScope.NotificationScrollingStack(
// completes.
if (
scrimOffset.value < 0 &&
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ (layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Gone,
+ ) ||
+ layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Lockscreen,
+ ))
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
} else if (
@@ -498,7 +503,7 @@ fun SceneScope.NotificationScrollingStack(
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
- .background(MaterialTheme.colorScheme.surface)
+ .thenIf(shouldShowScrim) { Modifier.background(scrimBackgroundColor) }
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
@@ -508,7 +513,7 @@ fun SceneScope.NotificationScrollingStack(
topBehavior = NestedScrollBehavior.EdgeWithPreview,
isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
- .thenIf(shadeMode == ShadeMode.Single) {
+ .thenIf(supportNestedScrolling) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
.stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
@@ -550,38 +555,6 @@ fun SceneScope.NotificationScrollingStack(
}
/**
- * This may be added to the lockscreen to provide a space to the start of the lock icon where the
- * short shelf has room to flow vertically below the lock icon, but to its start, allowing more
- * notifications to fit in the stack itself. (see: b/213934746)
- *
- * NOTE: this is totally unused for now; it is here to clarify the future plan
- */
-@Composable
-fun SceneScope.NotificationShelfSpace(
- viewModel: NotificationsPlaceholderViewModel,
- modifier: Modifier = Modifier,
-) {
- Text(
- text = "Shelf Space",
- modifier
- .element(key = Notifications.Elements.ShelfSpace)
- .fillMaxWidth()
- .onPlaced { coordinates: LayoutCoordinates ->
- debugLog(viewModel) {
- ("SHELF onPlaced:" +
- " size=${coordinates.size}" +
- " bounds=${coordinates.boundsInWindow()}")
- }
- }
- .clip(RoundedCornerShape(24.dp))
- .background(MaterialTheme.colorScheme.primaryContainer)
- .padding(16.dp),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onPrimaryContainer,
- )
-}
-
-/**
* A 0 height horizontal spacer to be placed at the bottom-most position in the current scene, where
* the notification contents (stack, footer, shelf) should be drawn.
*/
@@ -673,15 +646,19 @@ private suspend fun scrollNotificationStack(
}
}
+private fun TransitionState.isOnLockscreen(): Boolean {
+ return currentScene == Scenes.Lockscreen && currentOverlays.isEmpty()
+}
+
private fun shouldUseLockscreenStackBounds(state: TransitionState): Boolean {
- return state is TransitionState.Idle && state.currentScene == Scenes.Lockscreen
+ return state is TransitionState.Idle && state.isOnLockscreen()
}
private fun shouldUseLockscreenHunBounds(state: TransitionState): Boolean {
return when (state) {
- is TransitionState.Idle -> state.currentScene == Scenes.Lockscreen
+ is TransitionState.Idle -> state.isOnLockscreen()
is TransitionState.Transition ->
- state.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Lockscreen)
+ state.isTransitioning(from = quickSettingsShadeContentKey, to = Scenes.Lockscreen)
}
}
@@ -690,7 +667,7 @@ private fun shouldAnimateScrimCornerRadius(
shouldPunchHoleBehindScrim: Boolean,
): Boolean {
return shouldPunchHoleBehindScrim ||
- state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
}
private fun calculateCornerRadius(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index a22beccf3448..5b996704eb12 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -33,7 +33,6 @@ import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlay
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -69,9 +68,7 @@ constructor(
}
@Composable
- override fun ContentScope.Content(
- modifier: Modifier,
- ) {
+ override fun ContentScope.Content(modifier: Modifier) {
val viewModel =
rememberViewModel("NotificationsShadeOverlay-viewModel") {
contentViewModelFactory.create()
@@ -81,10 +78,7 @@ constructor(
viewModel.notificationsPlaceholderViewModelFactory.create()
}
- OverlayShade(
- modifier = modifier,
- onScrimClicked = viewModel::onScrimClicked,
- ) {
+ OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
Column {
ExpandedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -102,7 +96,8 @@ constructor(
shouldPunchHoleBehindScrim = false,
shouldFillMaxSize = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Dual,
+ shouldShowScrim = false,
+ supportNestedScrolling = false,
modifier = Modifier.fillMaxWidth(),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 630497998c3e..d75a776a2515 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -100,7 +100,6 @@ import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
@@ -114,11 +113,9 @@ import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class QuickSettingsScene
@Inject
@@ -420,17 +417,26 @@ private fun SceneScope.QuickSettingsScene(
.navigationBarsPadding()
.padding(horizontal = shadeHorizontalPadding),
)
+
+ // The minimum possible value for the top of the notification stack. In other words: how
+ // high is the notification stack allowed to get when the scene is at rest. It may still be
+ // translated farther upwards by a transition animation but, at rest, the top edge of its
+ // bounds must be limited to be at or below this value.
+ //
+ // A 1 pixel is added to compensate for any kind of rounding errors to make sure 100% that
+ // the notification stack is entirely "below" the entire screen.
+ val minNotificationStackTop = screenHeight.roundToInt() + 1
NotificationScrollingStack(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
- maxScrimTop = { screenHeight },
+ maxScrimTop = { minNotificationStackTop.toFloat() },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
shouldIncludeHeadsUpSpace = false,
- shadeMode = ShadeMode.Single,
+ supportNestedScrolling = true,
modifier =
Modifier.fillMaxWidth()
- .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+ .offset { IntOffset(x = 0, y = minNotificationStackTop) }
.padding(horizontal = shadeHorizontalPadding),
)
NotificationStackCutoffGuideline(
@@ -439,7 +445,7 @@ private fun SceneScope.QuickSettingsScene(
modifier =
Modifier.align(Alignment.BottomCenter)
.navigationBarsPadding()
- .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+ .offset { IntOffset(x = 0, y = minNotificationStackTop) }
.padding(horizontal = shadeHorizontalPadding),
)
}
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 dc9e267cb0e1..56de096effce 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
@@ -132,7 +132,6 @@ fun SceneContainer(
state = state,
modifier = modifier.fillMaxSize(),
swipeSourceDetector = viewModel.edgeDetector,
- gestureFilter = viewModel::shouldFilterGesture,
) {
sceneByKey.forEach { (sceneKey, scene) ->
scene(
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 ac58ab5296f6..8728521348de 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,5 +1,6 @@
package com.android.systemui.scene.ui.composable
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import com.android.compose.animation.scene.ProgressConverter
import com.android.compose.animation.scene.TransitionKey
@@ -12,11 +13,15 @@ import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShad
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview
+import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToDreamTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
@@ -44,10 +49,12 @@ val SceneContainerTransitions = transitions {
// Overscroll progress starts linearly with some resistance (3f) and slowly approaches 0.2f
defaultOverscrollProgressConverter = ProgressConverter.tanh(maxProgress = 0.2f, tilt = 3f)
+ defaultSwipeSpec = spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+ from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
@@ -68,6 +75,7 @@ val SceneContainerTransitions = transitions {
lockscreenToBouncerTransition()
}
from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
+ from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
from(Scenes.Lockscreen, to = Scenes.Shade, key = ToSplitShade) {
lockscreenToSplitShadeTransition()
@@ -87,6 +95,8 @@ val SceneContainerTransitions = transitions {
sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
}
+ from(Scenes.Communal, to = Scenes.Shade) { communalToShadeTransition() }
+ from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }
// Overlay transitions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
new file mode 100644
index 000000000000..d7173fd3cffb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToBouncerTransition.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.communalToBouncerTransition() {
+ toBouncerTransition()
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
new file mode 100644
index 000000000000..ba920acec0a5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromCommunalToShadeTransition.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.communalToShadeTransition() {
+ toShadeTransition()
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
new file mode 100644
index 000000000000..60dc4c05e99d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToGoneTransition.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
+
+fun TransitionBuilder.dreamToGoneTransition() {
+ spec = tween(durationMillis = 1000)
+
+ fade(Scenes.Dream.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 4c0efd2047ff..dd37b533ae4c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -1,29 +1,11 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.bouncer.ui.composable.Bouncer
-const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
-const val FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
-
fun TransitionBuilder.lockscreenToBouncerTransition() {
- spec = tween(durationMillis = 500)
-
- distance = UserActionDistance { fromSceneSize, _ ->
- fromSceneSize.height * FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION
- }
-
- translate(Bouncer.Elements.Content, y = 300.dp)
- fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Background)
- }
- fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
- fade(Bouncer.Elements.Content)
- }
+ toBouncerTransition()
}
fun TransitionBuilder.bouncerToLockscreenPreview() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
new file mode 100644
index 000000000000..7092e3f68f93
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToDreamTransition.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.shared.model.Scenes
+
+fun TransitionBuilder.lockscreenToDreamTransition() {
+ spec = tween(durationMillis = 1000)
+
+ fade(Scenes.Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
new file mode 100644
index 000000000000..de76f708c1c7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+const val TO_BOUNCER_FADE_FRACTION = 0.5f
+private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
+
+fun TransitionBuilder.toBouncerTransition() {
+ spec = tween(durationMillis = 500)
+
+ distance = UserActionDistance { fromSceneSize, _ ->
+ fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+ }
+
+ translate(Bouncer.Elements.Content, y = 300.dp)
+ fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) }
+ fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) }
+}
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 337f53a58844..23c4f12cb0ae 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
@@ -21,26 +21,20 @@ 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.notifications.ui.composable.Notifications
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.toNotificationsShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance = UserActionDistance { fromSceneSize, orientation ->
- fromSceneSize.height.toFloat() * 2 / 3f
- }
-
+ scaleSize(OverlayShade.Elements.Panel, height = 0f)
translate(OverlayShade.Elements.Panel, Edge.Top)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index c6c42fce7553..3ec057becc18 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -40,7 +40,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -89,7 +88,6 @@ import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
import com.android.systemui.media.dagger.MediaModule.QS_PANEL
@@ -117,7 +115,6 @@ import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
object Shade {
@@ -128,23 +125,13 @@ object Shade {
}
object Dimensions {
- val ScrimCornerSize = 32.dp
val HorizontalPadding = 16.dp
val ScrimOverscrollLimit = 32.dp
const val ScrimVisibilityThreshold = 5f
}
-
- object Shapes {
- val Scrim =
- RoundedCornerShape(
- topStart = Dimensions.ScrimCornerSize,
- topEnd = Dimensions.ScrimCornerSize,
- )
- }
}
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class ShadeScene
@Inject
@@ -197,11 +184,11 @@ constructor(
)
init {
- qqsMediaHost.expansion = MediaHostState.EXPANDED
+ qqsMediaHost.expansion = EXPANDED
qqsMediaHost.showsOnlyActiveMedia = true
qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
- qsMediaHost.expansion = MediaHostState.EXPANDED
+ qsMediaHost.expansion = EXPANDED
qsMediaHost.showsOnlyActiveMedia = false
qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
}
@@ -329,8 +316,7 @@ private fun SceneScope.SingleShade(
modifier =
modifier.thenIf(shouldPunchHoleBehindScrim) {
// Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
- // scene
- // (and not the one under it) during a scene transition.
+ // scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
) {
@@ -382,8 +368,8 @@ private fun SceneScope.SingleShade(
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { maxNotifScrimTop.toFloat() },
- shadeMode = ShadeMode.Single,
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+ supportNestedScrolling = true,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
@@ -601,7 +587,7 @@ private fun SceneScope.SplitShade(
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Split,
+ supportNestedScrolling = false,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 5e6f88ef8bc7..085157ac72b9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -124,10 +124,6 @@ internal class DraggableHandlerImpl(
overSlop: Float,
pointersDown: Int,
): DragController {
- if (startedPosition != null && layoutImpl.gestureFilter(startedPosition)) {
- return NoOpDragController
- }
-
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -189,7 +185,7 @@ internal class DraggableHandlerImpl(
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
+ internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
if (startedPosition == null) return null
return layoutImpl.swipeSourceDetector.source(
layoutSize = layoutImpl.lastSize,
@@ -199,7 +195,7 @@ internal class DraggableHandlerImpl(
)
}
- private fun resolveSwipe(
+ internal fun resolveSwipe(
pointersDown: Int,
fromSource: SwipeSource.Resolved?,
isUpOrLeft: Boolean,
@@ -230,7 +226,6 @@ internal class DraggableHandlerImpl(
val fromSource = resolveSwipeSource(startedPosition)
val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
-
return if (fromSource == null) {
Swipes(
upOrLeft = null,
@@ -370,10 +365,18 @@ private class DragControllerImpl(
return 0f
}
+ val currentTransitionIrreversible =
+ if (swipeAnimation.isUpOrLeft) {
+ swipes.upOrLeftResult?.isIrreversible ?: false
+ } else {
+ swipes.downOrRightResult?.isIrreversible ?: false
+ }
+
val needNewTransition =
- hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
- result.transitionKey != swipeAnimation.contentTransition.key
+ !currentTransitionIrreversible &&
+ (hasReachedToContent ||
+ result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+ result.transitionKey != swipeAnimation.contentTransition.key)
if (needNewTransition) {
// Make sure the current transition will finish to the right current scene.
@@ -559,6 +562,14 @@ internal class NestedScrollHandlerImpl(
val connection: PriorityNestedScrollConnection = nestedScrollConnection()
+ private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved {
+ return draggableHandler.resolveSwipe(
+ pointersDown = pointersDown,
+ fromSource = draggableHandler.resolveSwipeSource(startedPosition),
+ isUpOrLeft = isUpOrLeft,
+ )
+ }
+
private fun nestedScrollConnection(): PriorityNestedScrollConnection {
// If we performed a long gesture before entering priority mode, we would have to avoid
// moving on to the next scene.
@@ -575,36 +586,19 @@ internal class NestedScrollHandlerImpl(
val transitionState = layoutState.transitionState
val scene = transitionState.currentScene
val fromScene = layoutImpl.scene(scene)
- val nextScene =
+ val resolvedSwipe =
when {
- amount < 0f -> {
- val actionUpOrLeft =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Left
- Orientation.Vertical -> SwipeDirection.Resolved.Up
- },
- pointerCount = pointersInfo().pointersDown,
- fromSource = null,
- )
- fromScene.userActions[actionUpOrLeft]
- }
- amount > 0f -> {
- val actionDownOrRight =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Right
- Orientation.Vertical -> SwipeDirection.Resolved.Down
- },
- pointerCount = pointersInfo().pointersDown,
- fromSource = null,
- )
- fromScene.userActions[actionDownOrRight]
- }
+ amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true)
+ amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false)
else -> null
}
+ val nextScene =
+ resolvedSwipe?.let {
+ fromScene.userActions[it]
+ ?: if (it.fromSource != null) {
+ fromScene.userActions[it.copy(fromSource = null)]
+ } else null
+ }
if (nextScene != null) return true
if (transitionState !is TransitionState.Idle) return false
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 4ae97189bc02..dc3135ddbf54 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -213,8 +213,9 @@ internal class MultiPointerDraggableNode(
internal fun pointersInfo(): PointersInfo {
return PointersInfo(
+ // This may be null, i.e. when the user uses TalkBack
startedPosition = startedPosition,
- // Note: We could have 0 pointers during fling or for other reasons.
+ // We could have 0 pointers during fling or for other reasons.
pointersDown = pointersDown.coerceAtLeast(1),
)
}
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 6e89814a2dc2..c9a4d5808cac 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
@@ -47,9 +47,6 @@ import androidx.compose.ui.unit.LayoutDirection
* @param state the state of this layout.
* @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
* if any.
- * @param gestureFilter decides whether a drag gesture that started at the given start position
- * should be filtered. If the lambda returns `true`, the drag gesture will be ignored. If it
- * returns `false`, the drag gesture will be handled.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param builder the configuration of the different scenes and overlays of this layout.
@@ -60,7 +57,6 @@ fun SceneTransitionLayout(
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
- gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
builder: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -69,7 +65,6 @@ fun SceneTransitionLayout(
modifier,
swipeSourceDetector,
swipeDetector,
- gestureFilter,
transitionInterceptionThreshold,
onLayoutImpl = null,
builder,
@@ -503,6 +498,12 @@ sealed class UserActionResult(
* bigger than 100% when the user released their finger. `
*/
open val requiresFullDistanceSwipe: Boolean,
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can replace
+ * the action with the action for the opposite direction.
+ */
+ open val isIrreversible: Boolean = false,
) {
internal abstract fun toContent(currentScene: SceneKey): ContentKey
@@ -512,6 +513,7 @@ sealed class UserActionResult(
val toScene: SceneKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = toScene
}
@@ -521,6 +523,7 @@ sealed class UserActionResult(
val overlay: OverlayKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
+ override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
@@ -563,7 +566,14 @@ sealed class UserActionResult(
* the user released their finger.
*/
requiresFullDistanceSwipe: Boolean = false,
- ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
+
+ /**
+ * Whether swiping back in the opposite direction past the origin point of the swipe can
+ * replace the action with the action for the opposite direction.
+ */
+ isIrreversible: Boolean = false,
+ ): UserActionResult =
+ ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
/** A [UserActionResult] that shows [toOverlay]. */
operator fun invoke(
@@ -621,7 +631,6 @@ internal fun SceneTransitionLayoutForTesting(
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
- gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -638,7 +647,6 @@ internal fun SceneTransitionLayoutForTesting(
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
animationScope = animationScope,
- gestureFilter = gestureFilter,
)
.also { onLayoutImpl?.invoke(it) }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 9e7be37523f4..65c404387734 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -31,7 +31,6 @@ import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
@@ -71,7 +70,6 @@ internal class SceneTransitionLayoutImpl(
* animations.
*/
internal val animationScope: CoroutineScope,
- internal val gestureFilter: (startedPosition: Offset) -> Boolean,
) {
/**
* The map of [Scene]s.
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 b358faf2c418..879dc542552c 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
@@ -152,6 +152,7 @@ internal constructor(
internal val DefaultSwipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
+ dampingRatio = Spring.DampingRatioLowBouncy,
visibilityThreshold = OffsetVisibilityThreshold,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
index f758102fee47..54ee78366875 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
@@ -17,7 +17,6 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Stable
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange
/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
@@ -32,8 +31,6 @@ interface SwipeDetector {
val DefaultSwipeDetector = PassthroughSwipeDetector()
-val DefaultGestureFilter = { _: Offset -> false }
-
/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
class PassthroughSwipeDetector : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a6ebb0e3ba56..a3641e6635e7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -34,71 +34,76 @@ internal typealias SuspendedValue<T> = suspend () -> T
* Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
* after [onStart].
*
- * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ * @sample LargeTopAppBarNestedScrollConnection
+ * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
*/
class PriorityNestedScrollConnection(
- private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
- private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
+ orientation: Orientation,
+ private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
private val canScrollOnFling: Boolean,
- private val onStart: (offsetAvailable: Offset) -> Unit,
- private val onScroll: (offsetAvailable: Offset) -> Offset,
- private val onStop: (velocityAvailable: Velocity) -> SuspendedValue<Velocity>,
-) : NestedScrollConnection {
+ private val onStart: (offsetAvailable: Float) -> Unit,
+ private val onScroll: (offsetAvailable: Float) -> Float,
+ private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
+) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
private var isPriorityMode = false
- private var offsetScrolledBeforePriorityMode = Offset.Zero
+ private var offsetScrolledBeforePriorityMode = 0f
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
+ val availableFloat = available.toFloat()
// The offset before the start takes into account the up and down movements, starting from
// the beginning or from the last fling gesture.
- val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+ val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
if (
isPriorityMode ||
(source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
- !canStartPostScroll(available, offsetBeforeStart)
+ !canStartPostScroll(availableFloat, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- return onPriorityStart(available)
+ return onPriorityStart(availableFloat).toOffset()
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
if (source == NestedScrollSource.UserInput || canScrollOnFling) {
- if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
- return onPriorityStart(available)
+ val availableFloat = available.toFloat()
+ if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(availableFloat).toOffset()
}
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
}
return Offset.Zero
}
+ val availableFloat = available.toFloat()
if (!canContinueScroll(source)) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
- // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+ // We've just reset offsetScrolledBeforePriorityMode to 0f
// We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += available
+ offsetScrolledBeforePriorityMode += availableFloat
return Offset.Zero
}
// Step 2: We have the priority and can consume the scroll events.
- return onScroll(available)
+ return onScroll(availableFloat).toOffset()
}
override suspend fun onPreFling(available: Velocity): Velocity {
@@ -108,15 +113,16 @@ class PriorityNestedScrollConnection(
}
// Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
// of the fling gesture.
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity()
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ val availableFloat = available.toFloat()
if (isPriorityMode) {
- return onPriorityStop(velocity = available).invoke()
+ return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
}
- if (!canStartPostFling(available)) {
+ if (!canStartPostFling(availableFloat)) {
return Velocity.Zero
}
@@ -124,11 +130,11 @@ class PriorityNestedScrollConnection(
// given the available velocity.
// TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
// overscroll behavior on the Scene level.
- val smallOffset = Offset(available.x.sign, available.y.sign)
- onPriorityStart(available = smallOffset)
+ val smallOffset = availableFloat.sign
+ onPriorityStart(availableOffset = smallOffset)
// This is the last event of a scroll gesture.
- return onPriorityStop(available).invoke()
+ return onPriorityStop(availableFloat).invoke().toVelocity()
}
/**
@@ -138,10 +144,10 @@ class PriorityNestedScrollConnection(
*/
fun reset() {
// Step 3c: To ensure that an onStop is always called for every onStart.
- onPriorityStop(velocity = Velocity.Zero)
+ onPriorityStop(velocity = 0f)
}
- private fun onPriorityStart(available: Offset): Offset {
+ private fun onPriorityStart(availableOffset: Float): Float {
if (isPriorityMode) {
error("This should never happen, onPriorityStart() was called when isPriorityMode")
}
@@ -152,17 +158,17 @@ class PriorityNestedScrollConnection(
// Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
// lifted (step 3b), or this object has been destroyed (step 3c).
- onStart(available)
+ onStart(availableOffset)
- return onScroll(available)
+ return onScroll(availableOffset)
}
- private fun onPriorityStop(velocity: Velocity): SuspendedValue<Velocity> {
+ private fun onPriorityStop(velocity: Float): SuspendedValue<Float> {
// We can restart tracking the consumed offsets from scratch.
- offsetScrolledBeforePriorityMode = Offset.Zero
+ offsetScrolledBeforePriorityMode = 0f
if (!isPriorityMode) {
- return { Velocity.Zero }
+ return { 0f }
}
isPriorityMode = false
@@ -170,38 +176,3 @@ class PriorityNestedScrollConnection(
return onStop(velocity)
}
}
-
-fun PriorityNestedScrollConnection(
- orientation: Orientation,
- canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
- canStartPostFling: (velocityAvailable: Float) -> Boolean,
- canContinueScroll: (source: NestedScrollSource) -> Boolean,
- canScrollOnFling: Boolean,
- onStart: (offsetAvailable: Float) -> Unit,
- onScroll: (offsetAvailable: Float) -> Float,
- onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
-) =
- with(SpaceVectorConverter(orientation)) {
- PriorityNestedScrollConnection(
- canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset ->
- canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat())
- },
- canStartPostFling = { velocityAvailable: Velocity ->
- canStartPostFling(velocityAvailable.toFloat())
- },
- canContinueScroll = canContinueScroll,
- canScrollOnFling = canScrollOnFling,
- onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
- onScroll = { offsetAvailable: Offset ->
- onScroll(offsetAvailable.toFloat()).toOffset()
- },
- onStop = { velocityAvailable: Velocity ->
- val consumedVelocity = onStop(velocityAvailable.toFloat())
- suspend { consumedVelocity.invoke().toVelocity() }
- },
- )
- }
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 2c41b35da998..ecef6be49df8 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
@@ -109,8 +109,6 @@ class DraggableHandlerTest {
val transitionInterceptionThreshold = 0.05f
- var gestureFilter: (startedPosition: Offset) -> Boolean = DefaultGestureFilter
-
private val layoutImpl =
SceneTransitionLayoutImpl(
state = layoutState,
@@ -123,13 +121,16 @@ class DraggableHandlerTest {
// Use testScope and not backgroundScope here because backgroundScope does not
// work well with advanceUntilIdle(), which is used by some tests.
animationScope = testScope,
- gestureFilter = { startedPosition -> gestureFilter.invoke(startedPosition) },
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
+ var pointerInfoOwner: () -> PointersInfo = {
+ PointersInfo(startedPosition = Offset.Zero, pointersDown = 1)
+ }
+
fun nestedScrollConnection(
nestedScrollBehavior: NestedScrollBehavior,
isExternalOverscrollGesture: Boolean = false,
@@ -140,9 +141,7 @@ class DraggableHandlerTest {
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
isExternalOverscrollGesture = { isExternalOverscrollGesture },
- pointersInfoOwner = {
- PointersInfo(startedPosition = Offset.Zero, pointersDown = 1)
- },
+ pointersInfoOwner = { pointerInfoOwner() },
)
.connection
@@ -156,11 +155,18 @@ class DraggableHandlerTest {
fun downOffset(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) {
- error("upOffset() is required, not implemented yet")
+ error("use upOffset()")
} else {
Offset(x = 0f, y = down(fractionOfScreen))
}
+ fun upOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("use downOffset()")
+ } else {
+ Offset(x = 0f, y = up(fractionOfScreen))
+ }
+
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -343,13 +349,6 @@ class DraggableHandlerTest {
}
@Test
- fun onDragStarted_doesNotStartTransition_whenGestureFiltered() = runGestureTest {
- gestureFilter = { _ -> true }
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f), expectedConsumedOverSlop = 0f)
- assertIdle(currentScene = SceneA)
- }
-
- @Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
@@ -508,6 +507,54 @@ class DraggableHandlerTest {
}
@Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C.
+ val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it will replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneC,
+ progress = 0.3f,
+ )
+ }
+
+ @Test
+ fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
+ // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
+ mutableUserActionsA =
+ mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+ val dragController =
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+ overSlop = up(fractionOfScreen = 0.2f),
+ )
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = 0.2f,
+ )
+
+ // Reverse drag direction, it cannot replace the previous transition
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
+ assertTransition(
+ currentScene = SceneA,
+ fromScene = SceneA,
+ toScene = SceneB,
+ progress = -0.3f,
+ )
+ }
+
+ @Test
fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
navigateToSceneC()
@@ -1135,6 +1182,45 @@ class DraggableHandlerTest {
}
@Test
+ fun nestedScrollUseFromSourceInfo() = runGestureTest {
+ // Start at scene C.
+ navigateToSceneC()
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+
+ // Drag from the **top** of the screen
+ pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) }
+ assertIdle(currentScene = SceneC)
+
+ nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ // userAction: Swipe.Up to SceneB
+ toScene = SceneB,
+ progress = 0.1f,
+ )
+
+ // Reset to SceneC
+ nestedScroll.preFling(Velocity.Zero)
+ advanceUntilIdle()
+
+ // Drag from the **bottom** of the screen
+ pointerInfoOwner = {
+ PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1)
+ }
+ assertIdle(currentScene = SceneC)
+
+ nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ // userAction: Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
+ toScene = SceneA,
+ progress = 0.1f,
+ )
+ }
+
+ @Test
fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
@@ -1203,7 +1289,8 @@ class DraggableHandlerTest {
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneB) {}
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
@@ -1234,7 +1321,8 @@ class DraggableHandlerTest {
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
layoutState.transitions = transitions {
- from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
+ defaultSwipeSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
+ from(SceneA, to = SceneC) {}
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index bde769920676..badc43bd3e0f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,8 +18,9 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
@@ -35,13 +36,14 @@ class PriorityNestedScrollConnectionTest {
private var canStartPostFling = false
private var canContinueScroll = false
private var isStarted = false
- private var lastScroll: Offset? = null
- private var returnOnScroll = Offset.Zero
- private var lastStop: Velocity? = null
- private var returnOnStop = Velocity.Zero
+ private var lastScroll: Float? = null
+ private var returnOnScroll = 0f
+ private var lastStop: Float? = null
+ private var returnOnStop = 0f
private val scrollConnection =
PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
canStartPreScroll = { _, _ -> canStartPreScroll },
canStartPostScroll = { _, _ -> canStartPostScroll },
canStartPostFling = { canStartPostFling },
@@ -58,11 +60,6 @@ class PriorityNestedScrollConnectionTest {
},
)
- private val offset1 = Offset(1f, 1f)
- private val offset2 = Offset(2f, 2f)
- private val velocity1 = Velocity(1f, 1f)
- private val velocity2 = Velocity(2f, 2f)
-
@Test
fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
canStartPreScroll = true
@@ -70,7 +67,7 @@ class PriorityNestedScrollConnectionTest {
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -80,7 +77,7 @@ class PriorityNestedScrollConnectionTest {
scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
assertThat(isStarted).isEqualTo(false)
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(true)
}
@@ -89,7 +86,7 @@ class PriorityNestedScrollConnectionTest {
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
}
@@ -97,7 +94,7 @@ class PriorityNestedScrollConnectionTest {
fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
canStartPostScroll = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPreFling(available = Velocity.Zero)
@@ -115,7 +112,7 @@ class PriorityNestedScrollConnectionTest {
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
@@ -128,12 +125,12 @@ class PriorityNestedScrollConnectionTest {
canStartPostScroll = true
scrollConnection.onPostScroll(
- consumed = offset1,
- available = offset2,
- source = NestedScrollSource.Drag,
+ consumed = Offset(1f, 1f),
+ available = Offset(2f, 2f),
+ source = UserInput,
)
- assertThat(lastScroll).isEqualTo(offset2)
+ assertThat(lastScroll).isEqualTo(2f)
}
@Test
@@ -141,13 +138,13 @@ class PriorityNestedScrollConnectionTest {
startPriorityModePostScroll()
canContinueScroll = true
- scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput)
+ assertThat(lastScroll).isEqualTo(1f)
canContinueScroll = false
- scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
- assertThat(lastScroll).isNotEqualTo(offset2)
- assertThat(lastScroll).isEqualTo(offset1)
+ scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput)
+ assertThat(lastScroll).isNotEqualTo(2f)
+ assertThat(lastScroll).isEqualTo(1f)
}
@Test
@@ -155,7 +152,7 @@ class PriorityNestedScrollConnectionTest {
startPriorityModePostScroll()
canContinueScroll = false
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(lastStop).isNotNull()
}
@@ -184,22 +181,22 @@ class PriorityNestedScrollConnectionTest {
fun receive_onPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPostFling(consumed = velocity1, available = velocity2)
+ scrollConnection.onPostFling(consumed = Velocity(1f, 1f), available = Velocity(2f, 2f))
- assertThat(lastStop).isEqualTo(velocity2)
+ assertThat(lastStop).isEqualTo(2f)
}
@Test
fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
canStartPostFling = true
- scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
assertThat(isStarted).isEqualTo(false)
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
- source = NestedScrollSource.Drag,
+ source = UserInput,
)
assertThat(isStarted).isEqualTo(false)
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index c399abc7d81d..81d92faa40af 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -36,6 +36,7 @@ android_library {
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"kotlinx_coroutines",
+ "monet",
"dagger2",
"jsr330",
],
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
index 5eafbfc1f0b1..ec466f041179 100644
--- a/packages/SystemUI/customization/res/values/ids.xml
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -6,4 +6,13 @@
<item type="id" name="weather_clock_weather_icon" />
<item type="id" name="weather_clock_temperature" />
<item type="id" name="weather_clock_alarm_dnd" />
+
+ <item type="id" name="HOUR_DIGIT_PAIR"/>
+ <item type="id" name="MINUTE_DIGIT_PAIR"/>
+ <item type="id" name="HOUR_FIRST_DIGIT"/>
+ <item type="id" name="HOUR_SECOND_DIGIT"/>
+ <item type="id" name="MINUTE_FIRST_DIGIT"/>
+ <item type="id" name="MINUTE_SECOND_DIGIT"/>
+ <item type="id" name="TIME_FULL_FORMAT"/>
+ <item type="id" name="DATE_FORMAT"/>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 1863cd861c05..9877406eeac2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -62,6 +62,7 @@ constructor(
// implement the get method and ensure a value is returned before initialization is complete.
private var logger = DEFAULT_LOGGER
get() = field ?: DEFAULT_LOGGER
+
var messageBuffer: MessageBuffer
get() = logger.buffer
set(value) {
@@ -123,24 +124,24 @@ constructor(
attrs,
R.styleable.AnimatableClockView,
defStyleAttr,
- defStyleRes
+ defStyleRes,
)
try {
dozingWeightInternal =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_dozeWeight,
- /* default = */ 100
+ /* default = */ 100,
)
lockScreenWeightInternal =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_lockScreenWeight,
- /* default = */ 300
+ /* default = */ 300,
)
chargeAnimationDelay =
animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_chargeAnimationDelay,
- /* default = */ 200
+ /* default = */ 200,
)
} finally {
animatableClockViewAttributes.recycle()
@@ -151,14 +152,14 @@ constructor(
attrs,
android.R.styleable.TextView,
defStyleAttr,
- defStyleRes
+ defStyleRes,
)
try {
isSingleLineInternal =
textViewAttributes.getBoolean(
android.R.styleable.TextView_singleLine,
- /* default = */ false
+ /* default = */ false,
)
} finally {
textViewAttributes.recycle()
@@ -280,7 +281,7 @@ constructor(
text: CharSequence,
start: Int,
lengthBefore: Int,
- lengthAfter: Int
+ lengthAfter: Int,
) {
logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
super.onTextChanged(text, start, lengthBefore, lengthAfter)
@@ -305,7 +306,7 @@ constructor(
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = lockScreenWeight,
@@ -314,7 +315,7 @@ constructor(
interpolator = null,
duration = COLOR_ANIM_DURATION,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -327,7 +328,7 @@ constructor(
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = lockScreenWeight,
@@ -336,7 +337,7 @@ constructor(
duration = APPEAR_ANIM_DURATION,
interpolator = Interpolators.EMPHASIZED_DECELERATE,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -353,7 +354,7 @@ constructor(
interpolator = null,
duration = 0,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
setTextStyle(
weight = dozingWeightInternal,
@@ -362,7 +363,7 @@ constructor(
interpolator = Interpolators.EMPHASIZED_DECELERATE,
duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -381,7 +382,7 @@ constructor(
interpolator = null,
duration = CHARGE_ANIM_DURATION_PHASE_1,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
setTextStyle(
@@ -391,7 +392,7 @@ constructor(
interpolator = null,
duration = CHARGE_ANIM_DURATION_PHASE_0,
delay = chargeAnimationDelay.toLong(),
- onAnimationEnd = startAnimPhase2
+ onAnimationEnd = startAnimPhase2,
)
}
@@ -404,7 +405,7 @@ constructor(
interpolator = null,
duration = DOZE_ANIM_DURATION,
delay = 0,
- onAnimationEnd = null
+ onAnimationEnd = null,
)
}
@@ -444,7 +445,7 @@ constructor(
interpolator: TimeInterpolator?,
duration: Long,
delay: Long,
- onAnimationEnd: Runnable?
+ onAnimationEnd: Runnable?,
) {
textAnimator?.let {
it.setTextStyle(
@@ -454,7 +455,7 @@ constructor(
duration = duration,
interpolator = interpolator,
delay = delay,
- onAnimationEnd = onAnimationEnd
+ onAnimationEnd = onAnimationEnd,
)
it.glyphFilter = glyphFilter
}
@@ -468,7 +469,7 @@ constructor(
duration = duration,
interpolator = interpolator,
delay = delay,
- onAnimationEnd = onAnimationEnd
+ onAnimationEnd = onAnimationEnd,
)
textAnimator.glyphFilter = glyphFilter
}
@@ -476,6 +477,7 @@ constructor(
}
fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
+
fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
@@ -560,18 +562,11 @@ constructor(
* @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
* it finished moving.
*/
- fun offsetGlyphsForStepClockAnimation(
- distance: Float,
- fraction: Float,
- ) {
+ fun offsetGlyphsForStepClockAnimation(distance: Float, fraction: Float) {
for (i in 0 until NUM_DIGITS) {
val dir = if (isLayoutRtl) -1 else 1
val digitFraction =
- getDigitFraction(
- digit = i,
- isMovingToCenter = distance > 0,
- fraction = fraction,
- )
+ getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
val moveAmountForDigit = dir * distance * digitFraction
glyphOffsets[i] = moveAmountForDigit
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
new file mode 100644
index 000000000000..d001ef966c13
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
@@ -0,0 +1,448 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.util.TypedValue
+import com.android.internal.graphics.ColorUtils
+import com.android.internal.graphics.cam.Cam
+import com.android.internal.graphics.cam.CamUtils
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.monet.TonalPalette
+import java.io.IOException
+import kotlin.math.abs
+
+class AssetLoader
+private constructor(
+ private val pluginCtx: Context,
+ private val sysuiCtx: Context,
+ private val baseDir: String,
+ var colorScheme: ColorScheme?,
+ var seedColor: Int?,
+ var overrideChroma: Float?,
+ val typefaceCache: TypefaceCache,
+ val getThemeSeedColor: (Context) -> Int,
+ messageBuffer: MessageBuffer,
+) {
+ val logger = Logger(messageBuffer, TAG)
+ private val resources =
+ listOf(
+ Pair(pluginCtx.resources, pluginCtx.packageName),
+ Pair(sysuiCtx.resources, sysuiCtx.packageName),
+ )
+
+ constructor(
+ pluginCtx: Context,
+ sysuiCtx: Context,
+ baseDir: String,
+ messageBuffer: MessageBuffer,
+ getThemeSeedColor: ((Context) -> Int)? = null,
+ ) : this(
+ pluginCtx,
+ sysuiCtx,
+ baseDir,
+ colorScheme = null,
+ seedColor = null,
+ overrideChroma = null,
+ typefaceCache =
+ TypefaceCache(messageBuffer) { Typeface.createFromAsset(pluginCtx.assets, it) },
+ getThemeSeedColor = getThemeSeedColor ?: Companion::getThemeSeedColor,
+ messageBuffer = messageBuffer,
+ )
+
+ fun listAssets(path: String): List<String> {
+ return pluginCtx.resources.assets.list("$baseDir$path")?.toList() ?: emptyList()
+ }
+
+ fun tryReadString(resStr: String): String? = tryRead(resStr, ::readString)
+
+ fun readString(resStr: String): String {
+ val resPair = resolveResourceId(resStr)
+ if (resPair == null) {
+ throw IOException("Failed to parse string: $resStr")
+ }
+
+ val (res, id) = resPair
+ return res.getString(id)
+ }
+
+ fun tryReadColor(resStr: String): Int? = tryRead(resStr, ::readColor)
+
+ fun readColor(resStr: String): Int {
+ if (resStr.startsWith("#")) {
+ return Color.parseColor(resStr)
+ }
+
+ val schemeColor = tryParseColorFromScheme(resStr)
+ if (schemeColor != null) {
+ logColor("ColorScheme: $resStr", schemeColor)
+ return checkChroma(schemeColor)
+ }
+
+ val result = resolveColorResourceId(resStr)
+ if (result == null) {
+ throw IOException("Failed to parse color: $resStr")
+ }
+
+ val (res, colorId, targetTone) = result
+ val color = res.getColor(colorId)
+ if (targetTone == null || TonalPalette.SHADE_KEYS.contains(targetTone.toInt())) {
+ logColor("Resources: $resStr", color)
+ return checkChroma(color)
+ } else {
+ val interpolatedColor =
+ ColorStateList.valueOf(color)
+ .withLStar((1000f - targetTone) / 10f)
+ .getDefaultColor()
+ logColor("Resources (interpolated tone): $resStr", interpolatedColor)
+ return checkChroma(interpolatedColor)
+ }
+ }
+
+ private fun checkChroma(color: Int): Int {
+ return overrideChroma?.let {
+ val cam = Cam.fromInt(color)
+ val tone = CamUtils.lstarFromInt(color)
+ val result = ColorUtils.CAMToColor(cam.hue, it, tone)
+ logColor("Chroma override", result)
+ result
+ } ?: color
+ }
+
+ private fun tryParseColorFromScheme(resStr: String): Int? {
+ val colorScheme = this.colorScheme
+ if (colorScheme == null) {
+ logger.w("No color scheme available")
+ return null
+ }
+
+ val (packageName, category, name) = parseResourceId(resStr)
+ if (packageName != "android" || category != "color") {
+ logger.w("Failed to parse package from $resStr")
+ return null
+ }
+
+ var parts = name.split('_')
+ if (parts.size != 3) {
+ logger.w("Failed to find palette and shade from $name")
+ return null
+ }
+ val (_, paletteKey, shadeKeyStr) = parts
+
+ val palette =
+ when (paletteKey) {
+ "accent1" -> colorScheme.accent1
+ "accent2" -> colorScheme.accent2
+ "accent3" -> colorScheme.accent3
+ "neutral1" -> colorScheme.neutral1
+ "neutral2" -> colorScheme.neutral2
+ else -> return null
+ }
+
+ if (shadeKeyStr.contains("+") || shadeKeyStr.contains("-")) {
+ val signIndex = shadeKeyStr.indexOfLast { it == '-' || it == '+' }
+ // Use the tone of the seed color if it was set explicitly.
+ var baseTone =
+ if (seedColor != null) colorScheme.seedTone.toFloat()
+ else shadeKeyStr.substring(0, signIndex).toFloatOrNull()
+ val diff = shadeKeyStr.substring(signIndex).toFloatOrNull()
+
+ if (baseTone == null) {
+ logger.w("Failed to parse base tone from $shadeKeyStr")
+ return null
+ }
+
+ if (diff == null) {
+ logger.w("Failed to parse relative tone from $shadeKeyStr")
+ return null
+ }
+ return palette.getAtTone(baseTone + diff)
+ } else {
+ val shadeKey = shadeKeyStr.toIntOrNull()
+ if (shadeKey == null) {
+ logger.w("Failed to parse tone from $shadeKeyStr")
+ return null
+ }
+ return palette.allShadesMapped.get(shadeKey) ?: palette.getAtTone(shadeKey.toFloat())
+ }
+ }
+
+ fun readFontAsset(resStr: String): Typeface = typefaceCache.getTypeface(resStr)
+
+ fun tryReadTextAsset(path: String?): String? = tryRead(path, ::readTextAsset)
+
+ fun readTextAsset(path: String): String {
+ return pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
+ val buffer = ByteArray(stream.available())
+ stream.read(buffer)
+ String(buffer)
+ }
+ }
+
+ fun tryReadDrawableAsset(path: String?): Drawable? = tryRead(path, ::readDrawableAsset)
+
+ fun readDrawableAsset(path: String): Drawable {
+ var result: Drawable?
+
+ if (path.startsWith("@")) {
+ val pair = resolveResourceId(path)
+ if (pair == null) {
+ throw IOException("Failed to parse $path to an id")
+ }
+ val (res, id) = pair
+ result = res.getDrawable(id)
+ } else if (path.endsWith("xml")) {
+ // TODO(b/248609434): Support xml files in assets
+ throw IOException("Cannot load xml files from assets")
+ } else {
+ // Attempt to load as if it's a bitmap and directly loadable
+ result =
+ pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
+ Drawable.createFromResourceStream(
+ pluginCtx.resources,
+ TypedValue(),
+ stream,
+ null,
+ )
+ }
+ }
+
+ return result ?: throw IOException("Failed to load: $baseDir$path")
+ }
+
+ fun parseResourceId(resStr: String): Triple<String?, String, String> {
+ if (!resStr.startsWith("@")) {
+ throw IOException("Invalid resource id: $resStr; Must start with '@'")
+ }
+
+ // Parse out resource string
+ val parts = resStr.drop(1).split('/', ':')
+ return when (parts.size) {
+ 2 -> Triple(null, parts[0], parts[1])
+ 3 -> Triple(parts[0], parts[1], parts[2])
+ else -> throw IOException("Failed to parse resource string: $resStr")
+ }
+ }
+
+ fun resolveColorResourceId(resStr: String): Triple<Resources, Int, Float?>? {
+ var (packageName, category, name) = parseResourceId(resStr)
+
+ // Convert relative tonal specifiers to standard
+ val relIndex = name.indexOfLast { it == '_' }
+ val isToneRelative = name.contains("-") || name.contains("+")
+ val targetTone =
+ if (packageName != "android") {
+ null
+ } else if (isToneRelative) {
+ val signIndex = name.indexOfLast { it == '-' || it == '+' }
+ val baseTone = name.substring(relIndex + 1, signIndex).toFloatOrNull()
+ var diff = name.substring(signIndex).toFloatOrNull()
+ if (baseTone == null || diff == null) {
+ logger.w("Failed to parse relative tone from $name")
+ return null
+ }
+ baseTone + diff
+ } else {
+ val absTone = name.substring(relIndex + 1).toFloatOrNull()
+ if (absTone == null) {
+ logger.w("Failed to parse absolute tone from $name")
+ return null
+ }
+ absTone
+ }
+
+ if (
+ targetTone != null &&
+ (isToneRelative || !TonalPalette.SHADE_KEYS.contains(targetTone.toInt()))
+ ) {
+ val closeTone = TonalPalette.SHADE_KEYS.minBy { abs(it - targetTone) }
+ val prevName = name
+ name = name.substring(0, relIndex + 1) + closeTone
+ logger.i("Converted $prevName to $name")
+ }
+
+ val result = resolveResourceId(packageName, category, name)
+ if (result == null) {
+ return null
+ }
+
+ val (res, resId) = result
+ return Triple(res, resId, targetTone)
+ }
+
+ fun resolveResourceId(resStr: String): Pair<Resources, Int>? {
+ val (packageName, category, name) = parseResourceId(resStr)
+ return resolveResourceId(packageName, category, name)
+ }
+
+ fun resolveResourceId(
+ packageName: String?,
+ category: String,
+ name: String,
+ ): Pair<Resources, Int>? {
+ for ((res, ctxPkgName) in resources) {
+ val result = res.getIdentifier(name, category, packageName ?: ctxPkgName)
+ if (result != 0) {
+ return Pair(res, result)
+ }
+ }
+ return null
+ }
+
+ private fun <TArg : Any, TRes : Any> tryRead(arg: TArg?, fn: (TArg) -> TRes): TRes? {
+ try {
+ if (arg == null) {
+ return null
+ }
+ return fn(arg)
+ } catch (ex: IOException) {
+ logger.w("Failed to read $arg", ex)
+ return null
+ }
+ }
+
+ fun assetExists(path: String): Boolean {
+ try {
+ if (path.startsWith("@")) {
+ val pair = resolveResourceId(path)
+ val colorPair = resolveColorResourceId(path)
+ return pair != null || colorPair != null
+ } else {
+ val stream = pluginCtx.resources.assets.open("$baseDir$path")
+ if (stream == null) {
+ return false
+ }
+
+ stream.close()
+ return true
+ }
+ } catch (ex: IOException) {
+ return false
+ }
+ }
+
+ fun copy(messageBuffer: MessageBuffer? = null): AssetLoader =
+ AssetLoader(
+ pluginCtx,
+ sysuiCtx,
+ baseDir,
+ colorScheme,
+ seedColor,
+ overrideChroma,
+ typefaceCache,
+ getThemeSeedColor,
+ messageBuffer ?: logger.buffer,
+ )
+
+ fun setSeedColor(seedColor: Int?, style: MonetStyle?) {
+ this.seedColor = seedColor
+ refreshColorPalette(style)
+ }
+
+ fun refreshColorPalette(style: MonetStyle?) {
+ val seedColor =
+ this.seedColor ?: getThemeSeedColor(sysuiCtx).also { logColor("Theme Seed Color", it) }
+ this.colorScheme =
+ ColorScheme(
+ seedColor,
+ false, // darkTheme is not used for palette generation
+ style ?: MonetStyle.CLOCK,
+ )
+
+ // Enforce low chroma on output colors if low chroma theme is selected
+ this.overrideChroma = run {
+ val cam = colorScheme?.seed?.let { Cam.fromInt(it) }
+ if (cam != null && cam.chroma < LOW_CHROMA_LIMIT) {
+ return@run cam.chroma * LOW_CHROMA_SCALE
+ }
+ return@run null
+ }
+ }
+
+ fun getClockPaddingStart(): Int {
+ val result = resolveResourceId(null, "dimen", "clock_padding_start")
+ if (result != null) {
+ val (res, id) = result
+ return res.getDimensionPixelSize(id)
+ }
+ return -1
+ }
+
+ fun getStatusBarHeight(): Int {
+ val display = pluginCtx.getDisplayNoVerify()
+ if (display != null) {
+ return SystemBarUtils.getStatusBarHeight(pluginCtx.resources, display.cutout)
+ }
+
+ logger.w("No display available; falling back to android.R.dimen.status_bar_height")
+ val statusBarHeight = resolveResourceId("android", "dimen", "status_bar_height")
+ if (statusBarHeight != null) {
+ val (res, resId) = statusBarHeight
+ return res.getDimensionPixelSize(resId)
+ }
+
+ throw Exception("Could not fetch StatusBarHeight")
+ }
+
+ fun getResourcesId(name: String): Int = getResource("id", name) { _, id -> id }
+
+ fun getDimen(name: String): Int = getResource("dimen", name, Resources::getDimensionPixelSize)
+
+ fun getString(name: String): String = getResource("string", name, Resources::getString)
+
+ private fun <T> getResource(
+ category: String,
+ name: String,
+ getter: (res: Resources, id: Int) -> T,
+ ): T {
+ val result = resolveResourceId(null, category, name)
+ if (result != null) {
+ val (res, id) = result
+ if (id == -1) throw Exception("Cannot find id of $id from $TAG")
+ return getter(res, id)
+ }
+ throw Exception("Cannot find id of $name from $TAG")
+ }
+
+ private fun logColor(name: String, color: Int) {
+ if (DEBUG_COLOR) {
+ val cam = Cam.fromInt(color)
+ val tone = CamUtils.lstarFromInt(color)
+ logger.i("$name -> (hue: ${cam.hue}, chroma: ${cam.chroma}, tone: $tone)")
+ }
+ }
+
+ companion object {
+ private val DEBUG_COLOR = true
+ private val LOW_CHROMA_LIMIT = 15
+ private val LOW_CHROMA_SCALE = 1.5f
+ private val TAG = AssetLoader::class.simpleName!!
+
+ private fun getThemeSeedColor(ctx: Context): Int {
+ return ctx.resources.getColor(android.R.color.system_palette_key_color_primary_light)
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt
new file mode 100644
index 000000000000..5a041691b06a
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockAnimation.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+object ClockAnimation {
+ const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
new file mode 100644
index 000000000000..f5e843234095
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -0,0 +1,288 @@
+/*
+ * 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.shared.clocks
+
+import android.graphics.Point
+import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
+import com.android.internal.annotations.Keep
+import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.shared.clocks.view.HorizontalAlignment
+import com.android.systemui.shared.clocks.view.VerticalAlignment
+
+/** Data format for a simple asset-defined clock */
+@Keep
+data class ClockDesign(
+ val id: String,
+ val name: String? = null,
+ val description: String? = null,
+ val thumbnail: String? = null,
+ val large: ClockFace? = null,
+ val small: ClockFace? = null,
+ val colorPalette: MonetStyle? = null,
+)
+
+/** Describes a clock using layers */
+@Keep
+data class ClockFace(
+ val layers: List<ClockLayer> = listOf<ClockLayer>(),
+ val layerBounds: LayerBounds = LayerBounds.FIT,
+ val wallpaper: String? = null,
+ val faceLayout: DigitalFaceLayout? = null,
+ val pickerScale: ClockFaceScaleInPicker? = ClockFaceScaleInPicker(1.0f, 1.0f),
+)
+
+@Keep data class ClockFaceScaleInPicker(val scaleX: Float, val scaleY: Float)
+
+/** Base Type for a Clock Layer */
+@Keep
+interface ClockLayer {
+ /** Override of face LayerBounds setting for this layer */
+ val layerBounds: LayerBounds?
+}
+
+/** Clock layer that renders a static asset */
+@Keep
+data class AssetLayer(
+ /** Asset to render in this layer */
+ val asset: AssetReference,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** Clock layer that renders the time (or a component of it) using numerals */
+@Keep
+data class DigitalHandLayer(
+ /** See SimpleDateFormat for timespec format info */
+ val timespec: DigitalTimespec,
+ val style: TextStyle,
+ // adoStyle concrete type must match style,
+ // cause styles will transition between style and aodStyle
+ val aodStyle: TextStyle?,
+ val timer: Int? = null,
+ override val layerBounds: LayerBounds? = null,
+ var faceLayout: DigitalFaceLayout? = null,
+ // we pass 12-hour format from json, which will be converted to 24-hour format in codes
+ val dateTimeFormat: String,
+ val alignment: DigitalAlignment?,
+ // ratio of margins to measured size, currently used for handwritten clocks
+ val marginRatio: DigitalMarginRatio? = DigitalMarginRatio(),
+) : ClockLayer
+
+/** Clock layer that renders the time (or a component of it) using numerals */
+@Keep
+data class ComposedDigitalHandLayer(
+ val customizedView: String? = null,
+ /** See SimpleDateFormat for timespec format info */
+ val digitalLayers: List<DigitalHandLayer> = listOf<DigitalHandLayer>(),
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+@Keep
+data class DigitalAlignment(
+ val horizontalAlignment: HorizontalAlignment?,
+ val verticalAlignment: VerticalAlignment?,
+)
+
+@Keep
+data class DigitalMarginRatio(
+ val left: Float = 0F,
+ val top: Float = 0F,
+ val right: Float = 0F,
+ val bottom: Float = 0F,
+)
+
+/** Clock layer which renders a component of the time using an analog hand */
+@Keep
+data class AnalogHandLayer(
+ val timespec: AnalogTimespec,
+ val tickMode: AnalogTickMode,
+ val asset: AssetReference,
+ val timer: Int? = null,
+ val clock_pivot: Point = Point(0, 0),
+ val asset_pivot: Point? = null,
+ val length: Float = 1f,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** Clock layer which renders the time using an AVD */
+@Keep
+data class AnimatedHandLayer(
+ val timespec: AnalogTimespec,
+ val asset: AssetReference,
+ val timer: Int? = null,
+ override val layerBounds: LayerBounds? = null,
+) : ClockLayer
+
+/** A collection of asset references for use in different device modes */
+@Keep
+data class AssetReference(
+ val light: String,
+ val dark: String,
+ val doze: String? = null,
+ val lightTint: String? = null,
+ val darkTint: String? = null,
+ val dozeTint: String? = null,
+)
+
+/**
+ * Core TextStyling attributes for text clocks. Both color and sizing information can be applied to
+ * either subtype.
+ */
+@Keep
+interface TextStyle {
+ // fontSizeScale is a scale factor applied to the default clock's font size.
+ val fontSizeScale: Float?
+}
+
+/**
+ * This specifies a font and styling parameters for that font. This is rendered using a text view
+ * and the text animation classes used by the default clock. To ensure default value take effects,
+ * all parameters MUST have a default value
+ */
+@Keep
+data class FontTextStyle(
+ // Font to load and use in the TextView
+ val fontFamily: String? = null,
+ val lineHeight: Float? = null,
+ val borderWidth: String? = null,
+ // ratio of borderWidth / fontSize
+ val borderWidthScale: Float? = null,
+ // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
+ val fillColorLight: String? = null,
+ // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
+ val fillColorDark: String? = null,
+ override val fontSizeScale: Float? = null,
+ /**
+ * use `wdth` for width, `wght` for weight, 'opsz' for optical size single quote for tag name,
+ * and no quote for value separate different axis with `,` e.g. "'wght' 1000, 'wdth' 108, 'opsz'
+ * 90"
+ */
+ var fontVariation: String? = null,
+ // used when alternate in one font file is needed
+ var fontFeatureSettings: String? = null,
+ val renderType: RenderType = RenderType.STROKE_TEXT,
+ val outlineColor: String? = null,
+ val transitionDuration: Long = -1L,
+ val transitionInterpolator: InterpolatorEnum? = null,
+) : TextStyle
+
+/**
+ * As an alternative to using a font, we can instead render a digital clock using a set of drawables
+ * for each numeral, and optionally a colon. These drawables will be rendered directly after sizing
+ * and placing them. This may be easier than generating a font file in some cases, and is provided
+ * for ease of use. Unlike fonts, these are not localizable to other numeric systems (like Burmese).
+ */
+@Keep
+data class LottieTextStyle(
+ val numbers: List<String> = listOf(),
+ // Spacing between numbers, dimension string
+ val spacing: String = "0dp",
+ // Colon drawable may be omitted if unused in format spec
+ val colon: String? = null,
+ // key is keypath name to get strokes from lottie, value is the color name to query color in
+ // palette, e.g. @android:color/system_accent1_100
+ val fillColorLightMap: Map<String, String>? = null,
+ val fillColorDarkMap: Map<String, String>? = null,
+ override val fontSizeScale: Float? = null,
+ val paddingVertical: String = "0dp",
+ val paddingHorizontal: String = "0dp",
+) : TextStyle
+
+/** Layer sizing mode for the clockface or layer */
+enum class LayerBounds {
+ /**
+ * Sized so the larger dimension matches the allocated space. This results in some of the
+ * allocated space being unused.
+ */
+ FIT,
+
+ /**
+ * Sized so the smaller dimension matches the allocated space. This will clip some content to
+ * the edges of the space.
+ */
+ FILL,
+
+ /** Fills the allocated space exactly by stretching the layer */
+ STRETCH,
+}
+
+/** Ticking mode for analog hands. */
+enum class AnalogTickMode {
+ SWEEP,
+ TICK,
+}
+
+/** Timspec options for Analog Hands. Named for tick interval. */
+enum class AnalogTimespec {
+ SECONDS,
+ MINUTES,
+ HOURS,
+ HOURS_OF_DAY,
+ DAY_OF_WEEK,
+ DAY_OF_MONTH,
+ DAY_OF_YEAR,
+ WEEK,
+ MONTH,
+ TIMER,
+}
+
+enum class DigitalTimespec {
+ TIME_FULL_FORMAT,
+ DIGIT_PAIR,
+ FIRST_DIGIT,
+ SECOND_DIGIT,
+ DATE_FORMAT,
+}
+
+enum class DigitalFaceLayout {
+ // can only use HH_PAIR, MM_PAIR from DigitalTimespec
+ TWO_PAIRS_VERTICAL,
+ TWO_PAIRS_HORIZONTAL,
+ // can only use HOUR_FIRST_DIGIT, HOUR_SECOND_DIGIT, MINUTE_FIRST_DIGIT, MINUTE_SECOND_DIGIT
+ // from DigitalTimespec, used for tabular layout when the font doesn't support tnum
+ FOUR_DIGITS_ALIGN_CENTER,
+ FOUR_DIGITS_HORIZONTAL,
+}
+
+enum class RenderType {
+ CHANGE_WEIGHT,
+ HOLLOW_TEXT,
+ STROKE_TEXT,
+ OUTER_OUTLINE_TEXT,
+}
+
+enum class InterpolatorEnum(factory: () -> Interpolator) {
+ STANDARD({ Interpolators.STANDARD }),
+ EMPHASIZED({ Interpolators.EMPHASIZED });
+
+ val interpolator: Interpolator by lazy(factory)
+}
+
+fun generateDigitalLayerIdString(layer: DigitalHandLayer): String {
+ return if (
+ layer.timespec == DigitalTimespec.TIME_FULL_FORMAT ||
+ layer.timespec == DigitalTimespec.DATE_FORMAT
+ ) {
+ layer.timespec.toString()
+ } else {
+ if ("h" in layer.dateTimeFormat) {
+ "HOUR" + "_" + layer.timespec.toString()
+ } else {
+ "MINUTE" + "_" + layer.timespec.toString()
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 954155d16b05..9da3022fc0d8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -65,7 +65,7 @@ private val KNOWN_PLUGINS =
private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
key: TKey,
value: TVal,
- onNew: (TVal) -> Unit
+ onNew: (TVal) -> Unit,
): TVal {
val result = this.putIfAbsent(key, value)
if (result == null) {
@@ -110,7 +110,7 @@ open class ClockRegistry(
selfChange: Boolean,
uris: Collection<Uri>,
flags: Int,
- userId: Int
+ userId: Int,
) {
scope.launch(bgDispatcher) { querySettings() }
}
@@ -180,7 +180,7 @@ open class ClockRegistry(
override fun onPluginLoaded(
plugin: ClockProviderPlugin,
pluginContext: Context,
- manager: PluginLifecycleManager<ClockProviderPlugin>
+ manager: PluginLifecycleManager<ClockProviderPlugin>,
) {
plugin.initialize(clockBuffers)
@@ -218,7 +218,7 @@ open class ClockRegistry(
override fun onPluginUnloaded(
plugin: ClockProviderPlugin,
- manager: PluginLifecycleManager<ClockProviderPlugin>
+ manager: PluginLifecycleManager<ClockProviderPlugin>,
) {
for (clock in plugin.getClocks()) {
val id = clock.clockId
@@ -290,12 +290,12 @@ open class ClockRegistry(
Settings.Secure.getStringForUser(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- ActivityManager.getCurrentUser()
+ ActivityManager.getCurrentUser(),
)
} else {
Settings.Secure.getString(
context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
)
}
@@ -320,13 +320,13 @@ open class ClockRegistry(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
json,
- ActivityManager.getCurrentUser()
+ ActivityManager.getCurrentUser(),
)
} else {
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- json
+ json,
)
}
} catch (ex: Exception) {
@@ -418,7 +418,7 @@ open class ClockRegistry(
pluginManager.addPluginListener(
pluginListener,
ClockProviderPlugin::class.java,
- /*allowMultiple=*/ true
+ /*allowMultiple=*/ true,
)
scope.launch(bgDispatcher) { querySettings() }
@@ -427,7 +427,7 @@ open class ClockRegistry(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
/*notifyForDescendants=*/ false,
settingObserver,
- UserHandle.USER_ALL
+ UserHandle.USER_ALL,
)
ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG)
@@ -435,7 +435,7 @@ open class ClockRegistry(
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
/*notifyForDescendants=*/ false,
- settingObserver
+ settingObserver,
)
}
}
@@ -504,7 +504,7 @@ open class ClockRegistry(
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -516,7 +516,7 @@ open class ClockRegistry(
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -532,7 +532,7 @@ open class ClockRegistry(
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
- { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
@@ -548,7 +548,7 @@ open class ClockRegistry(
val isCurrent = currentClockId == info.metadata.clockId
logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" },
) {
str1 = info.metadata.clockId
str2 = info.manager.toString()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 4802e3447eb2..07191c671a34 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -34,7 +34,7 @@ class DefaultClockProvider(
val layoutInflater: LayoutInflater,
val resources: Resources,
val hasStepClockAnimation: Boolean = false,
- val migratedClocks: Boolean = false
+ val migratedClocks: Boolean = false,
) : ClockProvider {
private var messageBuffers: ClockMessageBuffers? = null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
new file mode 100644
index 000000000000..38697063bea5
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.TimeInterpolator
+import android.animation.ValueAnimator
+import android.graphics.Point
+
+class DigitTranslateAnimator(val updateCallback: () -> Unit) {
+ val DEFAULT_ANIMATION_DURATION = 500L
+ val updatedTranslate = Point(0, 0)
+
+ val baseTranslation = Point(0, 0)
+ var targetTranslation: Point? = null
+ val bounceAnimator: ValueAnimator =
+ ValueAnimator.ofFloat(1f).apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ addUpdateListener {
+ updateTranslation(it.animatedFraction, updatedTranslate)
+ updateCallback()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rebase()
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ rebase()
+ }
+ }
+ )
+ }
+
+ fun rebase() {
+ baseTranslation.x = updatedTranslate.x
+ baseTranslation.y = updatedTranslate.y
+ }
+
+ fun animatePosition(
+ animate: Boolean = true,
+ delay: Long = 0,
+ duration: Long = -1L,
+ interpolator: TimeInterpolator? = null,
+ targetTranslation: Point? = null,
+ onAnimationEnd: Runnable? = null,
+ ) {
+ this.targetTranslation = targetTranslation ?: Point(0, 0)
+ if (animate) {
+ bounceAnimator.cancel()
+ bounceAnimator.startDelay = delay
+ bounceAnimator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { bounceAnimator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onAnimationEnd.run()
+ bounceAnimator.removeListener(this)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ bounceAnimator.removeListener(this)
+ }
+ }
+ bounceAnimator.addListener(listener)
+ }
+ bounceAnimator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ updateTranslation(1F, updatedTranslate)
+ rebase()
+ updateCallback()
+ }
+ }
+
+ fun updateTranslation(progress: Float, outPoint: Point) {
+ outPoint.x =
+ (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt()
+ outPoint.y =
+ (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt()
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt
new file mode 100644
index 000000000000..2be6c6573ebe
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DimensionParser.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.util.TypedValue
+import java.util.regex.Pattern
+
+class DimensionParser(private val ctx: Context) {
+ fun convert(dimension: String?): Float? {
+ if (dimension == null) {
+ return null
+ }
+ return convert(dimension)
+ }
+
+ fun convert(dimension: String): Float {
+ val metrics = ctx.resources.displayMetrics
+ val (value, unit) = parse(dimension)
+ return TypedValue.applyDimension(unit, value, metrics)
+ }
+
+ fun parse(dimension: String): Pair<Float, Int> {
+ val matcher = parserPattern.matcher(dimension)
+ if (!matcher.matches()) {
+ throw NumberFormatException("Failed to parse '$dimension'")
+ }
+
+ val value =
+ matcher.group(1)?.toFloat() ?: throw NumberFormatException("Bad value in '$dimension'")
+ val unit =
+ dimensionMap.get(matcher.group(3) ?: "")
+ ?: throw NumberFormatException("Bad unit in '$dimension'")
+ return Pair(value, unit)
+ }
+
+ private companion object {
+ val parserPattern = Pattern.compile("(\\d+(\\.\\d+)?)([a-z]+)")
+ val dimensionMap =
+ mapOf(
+ "dp" to TypedValue.COMPLEX_UNIT_DIP,
+ "dip" to TypedValue.COMPLEX_UNIT_DIP,
+ "sp" to TypedValue.COMPLEX_UNIT_SP,
+ "px" to TypedValue.COMPLEX_UNIT_PX,
+ "pt" to TypedValue.COMPLEX_UNIT_PT,
+ "mm" to TypedValue.COMPLEX_UNIT_MM,
+ "in" to TypedValue.COMPLEX_UNIT_IN,
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt
new file mode 100644
index 000000000000..34cb4ef7089d
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LogUtil.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.Logger
+
+object LogUtil {
+ // Used when MessageBuffers are not provided by the host application
+ val DEFAULT_MESSAGE_BUFFER = LogcatOnlyMessageBuffer(LogLevel.INFO)
+
+ // Only intended for use during initialization steps where the correct logger doesn't exist yet
+ val FALLBACK_INIT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.ERROR), "CLOCK_INIT")
+
+ // Debug is primarially used for tests, but can also be used for tracking down hard issues.
+ val DEBUG_MESSAGE_BUFFER = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt
new file mode 100644
index 000000000000..f5a9375122b5
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TypefaceCache.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.graphics.Typeface
+import com.android.systemui.animation.TypefaceVariantCache
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
+
+class TypefaceCache(messageBuffer: MessageBuffer, val typefaceFactory: (String) -> Typeface) {
+ private val logger = Logger(messageBuffer, this::class.simpleName!!)
+
+ private data class CacheKey(val res: String, val fvar: String?)
+
+ private inner class WeakTypefaceRef(val key: CacheKey, typeface: Typeface) :
+ WeakReference<Typeface>(typeface, queue)
+
+ private var totalHits = 0
+
+ private var totalMisses = 0
+
+ private var totalEvictions = 0
+
+ // We use a map of WeakRefs here instead of an LruCache. This prevents needing to resize the
+ // cache depending on the number of distinct fonts used by a clock, as different clocks have
+ // different numbers of simultaneously loaded and configured fonts. Because our clocks tend to
+ // initialize a number of parallel views and animators, our usages of Typefaces overlap. As a
+ // result, once a typeface is no longer being used, it is unlikely to be recreated immediately.
+ private val cache = mutableMapOf<CacheKey, WeakTypefaceRef>()
+ private val queue = ReferenceQueue<Typeface>()
+
+ fun getTypeface(res: String): Typeface {
+ checkQueue()
+ val key = CacheKey(res, null)
+ cache.get(key)?.get()?.let {
+ logHit(key)
+ return it
+ }
+
+ logMiss(key)
+ val result = typefaceFactory(res)
+ cache.put(key, WeakTypefaceRef(key, result))
+ return result
+ }
+
+ fun getVariantCache(res: String): TypefaceVariantCache {
+ val baseTypeface = getTypeface(res)
+ return object : TypefaceVariantCache {
+ override fun getTypefaceForVariant(fvar: String?): Typeface? {
+ checkQueue()
+ val key = CacheKey(res, fvar)
+ cache.get(key)?.get()?.let {
+ logHit(key)
+ return it
+ }
+
+ logMiss(key)
+ return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also {
+ cache.put(key, WeakTypefaceRef(key, it))
+ }
+ }
+ }
+ }
+
+ private fun logHit(key: CacheKey) {
+ totalHits++
+ if (DEBUG_HITS)
+ logger.i({ "HIT: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalHits
+ }
+ }
+
+ private fun logMiss(key: CacheKey) {
+ totalMisses++
+ logger.w({ "MISS: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalMisses
+ }
+ }
+
+ private fun logEviction(key: CacheKey) {
+ totalEvictions++
+ logger.i({ "EVICTED: $str1; Total: $int1" }) {
+ str1 = key.toString()
+ int1 = totalEvictions
+ }
+ }
+
+ private fun checkQueue() =
+ generateSequence { queue.poll() }
+ .filterIsInstance<WeakTypefaceRef>()
+ .forEach {
+ logEviction(it.key)
+ cache.remove(it.key)
+ }
+
+ companion object {
+ private val DEBUG_HITS = false
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
new file mode 100644
index 000000000000..eb7234646a64
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.shared.clocks.view
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Point
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.LogUtil
+import java.util.Locale
+
+abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) {
+ protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+ get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
+
+ abstract var digitalClockTextViewMap: MutableMap<Int, SimpleDigitalClockTextView>
+
+ @VisibleForTesting
+ var isAnimationEnabled = true
+ set(value) {
+ field = value
+ digitalClockTextViewMap.forEach { _, view -> view.isAnimationEnabled = value }
+ }
+
+ var dozeFraction: Float = 0F
+ set(value) {
+ field = value
+ digitalClockTextViewMap.forEach { _, view -> view.dozeFraction = field }
+ }
+
+ val dozeControlState = DozeControlState()
+
+ var isReactiveTouchInteractionEnabled = false
+ set(value) {
+ field = value
+ }
+
+ open val text: String?
+ get() = null
+
+ open fun refreshTime() = logger.d("refreshTime()")
+
+ override fun invalidate() {
+ logger.d("invalidate()")
+ super.invalidate()
+ }
+
+ override fun requestLayout() {
+ logger.d("requestLayout()")
+ super.requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure()")
+ calculateSize(widthMeasureSpec, heightMeasureSpec)?.let { setMeasuredDimension(it.x, it.y) }
+ ?: run { super.onMeasure(widthMeasureSpec, heightMeasureSpec) }
+ calculateLeftTopPosition()
+ dozeControlState.animateReady = true
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ logger.d("onLayout()")
+ super.onLayout(changed, left, top, right, bottom)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ text?.let { logger.d({ "onDraw($str1)" }) { str1 = it } } ?: run { logger.d("onDraw()") }
+ super.onDraw(canvas)
+ }
+
+ /*
+ * Called in onMeasure to generate width/height overrides to the normal measuring logic. A null
+ * result causes the normal view measuring logic to execute.
+ */
+ protected open fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point? = null
+
+ protected open fun calculateLeftTopPosition() {}
+
+ override fun addView(child: View?) {
+ if (child == null) return
+ logger.d({ "addView($str1 @$int1)" }) {
+ str1 = child::class.simpleName!!
+ int1 = child.id
+ }
+ super.addView(child)
+ if (child is SimpleDigitalClockTextView) {
+ digitalClockTextViewMap[child.id] = child
+ }
+ child.setWillNotDraw(true)
+ }
+
+ open fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ digitalClockTextViewMap.forEach { _, view -> view.animateDoze(isDozing, isAnimated) }
+ }
+
+ open fun animateCharge() {
+ digitalClockTextViewMap.forEach { _, view -> view.animateCharge() }
+ }
+
+ open fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+
+ fun updateColors(assets: AssetLoader, isRegionDark: Boolean) {
+ digitalClockTextViewMap.forEach { _, view -> view.updateColors(assets, isRegionDark) }
+ invalidate()
+ }
+
+ fun onFontSettingChanged(fontSizePx: Float) {
+ digitalClockTextViewMap.forEach { _, view -> view.applyTextSize(fontSizePx) }
+ }
+
+ open val hasCustomWeatherDataDisplay
+ get() = false
+
+ open val hasCustomPositionUpdatedAnimation
+ get() = false
+
+ /** True if it's large weather clock, will use weatherBlueprint in compose */
+ open val useCustomClockScene
+ get() = false
+
+ // TODO: implement ClockEventUnion?
+ open fun onLocaleChanged(locale: Locale) {}
+
+ open fun onWeatherDataChanged(data: WeatherData) {}
+
+ open fun onAlarmDataChanged(data: AlarmData) {}
+
+ open fun onZenDataChanged(data: ZenData) {}
+
+ open fun onPickerCarouselSwiping(swipingFraction: Float) {}
+
+ open fun isAlignedWithScreen(): Boolean = false
+
+ /**
+ * animateDoze needs correct translate value, which is calculated in onMeasure so we need to
+ * delay this animation when we get correct values
+ */
+ class DozeControlState {
+ var animateDoze: () -> Unit = {}
+ set(value) {
+ if (animateReady) {
+ value()
+ field = {}
+ } else {
+ field = value
+ }
+ }
+
+ var animateReady = false
+ set(value) {
+ if (value) {
+ animateDoze()
+ animateDoze = {}
+ }
+ field = value
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
new file mode 100644
index 000000000000..c29c8dac8ba6
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.shared.clocks.view
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Point
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import com.android.app.animation.Interpolators
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.FontTextStyle
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal)
+
+class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffer: MessageBuffer) :
+ DigitalClockFaceView(context, messageBuffer) {
+ override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
+ val digitLeftTopMap = mutableMapOf<Int, Point>()
+ var maxSingleDigitHeight = -1
+ var maxSingleDigitWidth = -1
+ val lockscreenTranslate = Point(0, 0)
+ val aodTranslate = Point(0, 0)
+
+ init {
+ setWillNotDraw(false)
+ layoutParams =
+ RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ }
+
+ private var prevX = 0f
+ private var prevY = 0f
+ private var isDown = false
+
+ // TODO(b/340253296): Genericize; json spec
+ private var wght = 603f
+ private var wdth = 100f
+
+ // TODO(b/340253296): Json spec
+ private val MAX_WGHT = 950f
+ private val MIN_WGHT = 50f
+ private val WGHT_SCALE = 0.5f
+
+ private val MAX_WDTH = 150f
+ private val MIN_WDTH = 0f
+ private val WDTH_SCALE = 0.2f
+
+ override fun onTouchEvent(evt: MotionEvent): Boolean {
+ // TODO(b/340253296): implement on DigitalClockFaceView?
+ if (!isReactiveTouchInteractionEnabled) {
+ return super.onTouchEvent(evt)
+ }
+
+ when (evt.action) {
+ MotionEvent.ACTION_DOWN -> {
+ isDown = true
+ prevX = evt.x
+ prevY = evt.y
+ return true
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ if (!isDown) {
+ return super.onTouchEvent(evt)
+ }
+
+ wdth = clamp(wdth + (evt.x - prevX) * WDTH_SCALE, MIN_WDTH, MAX_WDTH)
+ wght = clamp(wght + (evt.y - prevY) * WGHT_SCALE, MIN_WGHT, MAX_WGHT)
+ prevX = evt.x
+ prevY = evt.y
+
+ // TODO(b/340253296): Genericize; json spec
+ val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100"
+ digitalClockTextViewMap.forEach { (_, view) ->
+ val textStyle = view.textStyle as FontTextStyle
+ textStyle.fontVariation = fvar
+ view.applyStyles(assetLoader, textStyle, view.aodStyle)
+ }
+
+ requestLayout()
+ invalidate()
+ return true
+ }
+
+ MotionEvent.ACTION_UP -> {
+ isDown = false
+ return true
+ }
+ }
+
+ return super.onTouchEvent(evt)
+ }
+
+ override fun addView(child: View?) {
+ super.addView(child)
+ (child as SimpleDigitalClockTextView).digitTranslateAnimator =
+ DigitTranslateAnimator(::invalidate)
+ }
+
+ protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
+ digitalClockTextViewMap.forEach { (_, textView) ->
+ textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ }
+ val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
+ maxSingleDigitHeight = textView.measuredHeight
+ maxSingleDigitWidth = textView.measuredWidth
+ aodTranslate.x = -(maxSingleDigitWidth * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
+ aodTranslate.y = (maxSingleDigitHeight * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
+ return Point(
+ ((maxSingleDigitWidth + abs(aodTranslate.x)) * 2),
+ ((maxSingleDigitHeight + abs(aodTranslate.y)) * 2),
+ )
+ }
+
+ protected override fun calculateLeftTopPosition() {
+ digitLeftTopMap[R.id.HOUR_FIRST_DIGIT] = Point(0, 0)
+ digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitWidth, 0)
+ digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitHeight)
+ digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitWidth, maxSingleDigitHeight)
+ digitLeftTopMap.forEach { _, point ->
+ point.x += abs(aodTranslate.x)
+ point.y += abs(aodTranslate.y)
+ }
+ }
+
+ override fun refreshTime() {
+ super.refreshTime()
+ digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ digitalClockTextViewMap.forEach { (id, _) ->
+ val textView = digitalClockTextViewMap[id]!!
+ canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat())
+ textView.draw(canvas)
+ canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat())
+ }
+ }
+
+ override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ dozeControlState.animateDoze = {
+ super.animateDoze(isDozing, isAnimated)
+ if (maxSingleDigitHeight == -1) {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ }
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ textView.digitTranslateAnimator?.let {
+ if (!isDozing) {
+ it.animatePosition(
+ animate = isAnimated && isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = AOD_TRANSITION_DURATION,
+ targetTranslation =
+ updateDirectionalTargetTranslate(id, lockscreenTranslate),
+ )
+ } else {
+ it.animatePosition(
+ animate = isAnimated && isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = AOD_TRANSITION_DURATION,
+ onAnimationEnd = null,
+ targetTranslation = updateDirectionalTargetTranslate(id, aodTranslate),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ override fun animateCharge() {
+ super.animateCharge()
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ textView.digitTranslateAnimator?.let {
+ it.animatePosition(
+ animate = isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = CHARGING_TRANSITION_DURATION,
+ onAnimationEnd = {
+ it.animatePosition(
+ animate = isAnimationEnabled,
+ interpolator = Interpolators.EMPHASIZED,
+ duration = CHARGING_TRANSITION_DURATION,
+ targetTranslation =
+ updateDirectionalTargetTranslate(
+ id,
+ if (dozeFraction == 1F) aodTranslate else lockscreenTranslate,
+ ),
+ )
+ },
+ targetTranslation =
+ updateDirectionalTargetTranslate(
+ id,
+ if (dozeFraction == 1F) lockscreenTranslate else aodTranslate,
+ ),
+ )
+ }
+ }
+ }
+
+ companion object {
+ val AOD_TRANSITION_DURATION = 750L
+ val CHARGING_TRANSITION_DURATION = 300L
+
+ val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F
+ val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
+
+ // Use the sign of targetTranslation to control the direction of digit translation
+ fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
+ val outPoint = Point(targetTranslation)
+ when (id) {
+ R.id.HOUR_FIRST_DIGIT -> {
+ outPoint.x *= -1
+ outPoint.y *= -1
+ }
+
+ R.id.HOUR_SECOND_DIGIT -> {
+ outPoint.x *= 1
+ outPoint.y *= -1
+ }
+
+ R.id.MINUTE_FIRST_DIGIT -> {
+ outPoint.x *= -1
+ outPoint.y *= 1
+ }
+
+ R.id.MINUTE_SECOND_DIGIT -> {
+ outPoint.x *= 1
+ outPoint.y *= 1
+ }
+ }
+ return outPoint
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
new file mode 100644
index 000000000000..74617b1c0c5c
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -0,0 +1,654 @@
+/*
+ * 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.shared.clocks.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Point
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.text.Layout
+import android.text.TextPaint
+import android.util.AttributeSet
+import android.util.Log
+import android.util.MathUtils
+import android.util.TypedValue
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.animation.Interpolator
+import android.widget.TextView
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.TextAnimator
+import com.android.systemui.animation.TypefaceVariantCache
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.ClockAnimation
+import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.DimensionParser
+import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.LogUtil
+import com.android.systemui.shared.clocks.RenderType
+import com.android.systemui.shared.clocks.TextStyle
+import java.lang.Thread
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.min
+
+private val TAG = SimpleDigitalClockTextView::class.simpleName!!
+
+@SuppressLint("AppCompatCustomView")
+open class SimpleDigitalClockTextView(
+ ctx: Context,
+ messageBuffer: MessageBuffer,
+ attrs: AttributeSet? = null,
+) : TextView(ctx, attrs), SimpleDigitalClockView {
+ val lockScreenPaint = TextPaint()
+ override lateinit var textStyle: FontTextStyle
+ lateinit var aodStyle: FontTextStyle
+ private val parser = DimensionParser(ctx)
+ var maxSingleDigitHeight = -1
+ var maxSingleDigitWidth = -1
+ var digitTranslateAnimator: DigitTranslateAnimator? = null
+ var aodFontSizePx: Float = -1F
+ var isVertical: Boolean = false
+
+ // Store the font size when there's no height constraint as a reference when adjusting font size
+ private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
+ // Calculated by height of styled text view / text size
+ // Used as a factor to calculate a smaller font size when text height is constrained
+ @VisibleForTesting var fontSizeAdjustFactor = 1F
+
+ private val initThread = Thread.currentThread()
+
+ // textBounds is the size of text in LS, which only measures current text in lockscreen style
+ var textBounds = Rect()
+ // prevTextBounds and targetTextBounds are to deal with dozing animation between LS and AOD
+ // especially for the textView which has different bounds during the animation
+ // prevTextBounds holds the state we are transitioning from
+ private val prevTextBounds = Rect()
+ // targetTextBounds holds the state we are interpolating to
+ private val targetTextBounds = Rect()
+ protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+ get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
+
+ private var aodDozingInterpolator: Interpolator? = null
+
+ @VisibleForTesting lateinit var textAnimator: TextAnimator
+ @VisibleForTesting var outlineAnimator: TextAnimator? = null
+ // used for hollow style for AOD version
+ // because stroke style for some fonts have some unwanted inner strokes
+ // we want to draw this layer on top to oclude them
+ @VisibleForTesting var innerAnimator: TextAnimator? = null
+
+ lateinit var typefaceCache: TypefaceVariantCache
+ private set
+
+ private fun setTypefaceCache(value: TypefaceVariantCache) {
+ typefaceCache = value
+ if (this::textAnimator.isInitialized) {
+ textAnimator.typefaceCache = value
+ }
+ outlineAnimator?.typefaceCache = value
+ innerAnimator?.typefaceCache = value
+ }
+
+ @VisibleForTesting
+ var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+ TextAnimator(layout, ClockAnimation.NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb).also {
+ if (this::typefaceCache.isInitialized) {
+ it.typefaceCache = typefaceCache
+ }
+ }
+ }
+
+ override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER
+ override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
+ override var isAnimationEnabled = true
+ override var dozeFraction: Float = 0F
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ // Have to passthrough to unify View with SimpleDigitalClockView
+ override var text: String
+ get() = super.getText().toString()
+ set(value) = super.setText(value)
+
+ var textBorderWidth = 0F
+ var aodBorderWidth = 0F
+ var baselineFromMeasure = 0
+
+ var textFillColor: Int? = null
+ var textOutlineColor = TEXT_OUTLINE_DEFAULT_COLOR
+ var aodFillColor = AOD_DEFAULT_COLOR
+ var aodOutlineColor = AOD_OUTLINE_DEFAULT_COLOR
+
+ override fun updateColors(assets: AssetLoader, isRegionDark: Boolean) {
+ val fillColor = if (isRegionDark) textStyle.fillColorLight else textStyle.fillColorDark
+ textFillColor =
+ fillColor?.let { assets.readColor(it) }
+ ?: assets.seedColor
+ ?: getDefaultColor(assets, isRegionDark)
+ // for NumberOverlapView to read correct color
+ lockScreenPaint.color = textFillColor as Int
+ textStyle.outlineColor?.let { textOutlineColor = assets.readColor(it) }
+ ?: run { textOutlineColor = TEXT_OUTLINE_DEFAULT_COLOR }
+ (aodStyle.fillColorLight ?: aodStyle.fillColorDark)?.let {
+ aodFillColor = assets.readColor(it)
+ } ?: run { aodFillColor = AOD_DEFAULT_COLOR }
+ aodStyle.outlineColor?.let { aodOutlineColor = assets.readColor(it) }
+ ?: run { aodOutlineColor = AOD_OUTLINE_DEFAULT_COLOR }
+ if (dozeFraction < 1f) {
+ textAnimator.setTextStyle(color = textFillColor, animate = false)
+ outlineAnimator?.setTextStyle(color = textOutlineColor, animate = false)
+ }
+ invalidate()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure()")
+ if (isVertical) {
+ // use at_most to avoid apply measuredWidth from last measuring to measuredHeight
+ // cause we use max to setMeasuredDimension
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST),
+ MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST),
+ )
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+
+ val layout = this.layout
+ if (layout != null) {
+ if (!this::textAnimator.isInitialized) {
+ textAnimator = textAnimatorFactory(layout, ::invalidate)
+ outlineAnimator = textAnimatorFactory(layout) {}
+ innerAnimator = textAnimatorFactory(layout) {}
+ setInterpolatorPaint()
+ } else {
+ textAnimator.updateLayout(layout)
+ outlineAnimator?.updateLayout(layout)
+ innerAnimator?.updateLayout(layout)
+ }
+ baselineFromMeasure = layout.getLineBaseline(0)
+ } else {
+ val currentThread = Thread.currentThread()
+ Log.wtf(
+ TAG,
+ "TextView.getLayout() is null after measure! " +
+ "currentThread=$currentThread; initThread=$initThread",
+ )
+ }
+
+ var expectedWidth: Int
+ var expectedHeight: Int
+
+ if (MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+ // For view which has fixed height, e.g. small clock,
+ // we should always return the size required from parent view
+ expectedHeight = heightMeasureSpec
+ } else {
+ expectedHeight =
+ MeasureSpec.makeMeasureSpec(
+ if (isSingleDigit()) {
+ maxSingleDigitHeight
+ } else {
+ textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt()
+ },
+ MeasureSpec.getMode(measuredHeight),
+ )
+ }
+ if (MeasureSpec.getMode(widthMeasureSpec) == EXACTLY) {
+ expectedWidth = widthMeasureSpec
+ } else {
+ expectedWidth =
+ MeasureSpec.makeMeasureSpec(
+ if (isSingleDigit()) {
+ maxSingleDigitWidth
+ } else {
+ max(
+ textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
+ MeasureSpec.getSize(measuredWidth),
+ )
+ },
+ MeasureSpec.getMode(measuredWidth),
+ )
+ }
+
+ if (isVertical) {
+ expectedWidth = expectedHeight.also { expectedHeight = expectedWidth }
+ }
+ setMeasuredDimension(expectedWidth, expectedHeight)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (isVertical) {
+ canvas.save()
+ canvas.translate(0F, measuredHeight.toFloat())
+ canvas.rotate(-90F)
+ }
+ logger.d({ "onDraw(); ls: $str1; aod: $str2;" }) {
+ str1 = textAnimator.textInterpolator.shapedText
+ str2 = outlineAnimator?.textInterpolator?.shapedText
+ }
+ val translation = getLocalTranslation()
+ canvas.translate(translation.x.toFloat(), translation.y.toFloat())
+ digitTranslateAnimator?.let {
+ canvas.translate(it.updatedTranslate.x.toFloat(), it.updatedTranslate.y.toFloat())
+ }
+
+ if (aodStyle.renderType == RenderType.HOLLOW_TEXT) {
+ canvas.saveLayer(
+ -translation.x.toFloat(),
+ -translation.y.toFloat(),
+ (-translation.x + measuredWidth).toFloat(),
+ (-translation.y + measuredHeight).toFloat(),
+ null,
+ )
+ outlineAnimator?.draw(canvas)
+ canvas.saveLayer(
+ -translation.x.toFloat(),
+ -translation.y.toFloat(),
+ (-translation.x + measuredWidth).toFloat(),
+ (-translation.y + measuredHeight).toFloat(),
+ Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) },
+ )
+ innerAnimator?.draw(canvas)
+ canvas.restore()
+ canvas.restore()
+ } else if (aodStyle.renderType != RenderType.CHANGE_WEIGHT) {
+ outlineAnimator?.draw(canvas)
+ }
+ textAnimator.draw(canvas)
+
+ digitTranslateAnimator?.let {
+ canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat())
+ }
+ canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
+ if (isVertical) {
+ canvas.restore()
+ }
+ }
+
+ override fun invalidate() {
+ logger.d("invalidate()")
+ super.invalidate()
+ (parent as? DigitalClockFaceView)?.invalidate()
+ }
+
+ override fun refreshTime() {
+ logger.d("refreshTime()")
+ refreshText()
+ }
+
+ override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ if (!this::textAnimator.isInitialized) {
+ return
+ }
+ val fvar = if (isDozing) aodStyle.fontVariation else textStyle.fontVariation
+ textAnimator.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = if (isDozing) aodFillColor else textFillColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ updateTextBoundsForTextAnimator()
+ outlineAnimator?.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = if (isDozing) aodOutlineColor else textOutlineColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ strokeWidth = if (isDozing) aodBorderWidth else textBorderWidth,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ innerAnimator?.setTextStyle(
+ animate = isAnimated && isAnimationEnabled,
+ color = Color.WHITE,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ fvar = fvar,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ )
+ }
+
+ override fun animateCharge() {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ // Skip charge animation if dozing animation is already playing.
+ return
+ }
+ logger.d("animateCharge()")
+ val middleFvar = if (dozeFraction == 0F) aodStyle.fontVariation else textStyle.fontVariation
+ val endFvar = if (dozeFraction == 0F) textStyle.fontVariation else aodStyle.fontVariation
+ val startAnimPhase2 = Runnable {
+ textAnimator.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ outlineAnimator?.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ innerAnimator?.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+ updateTextBoundsForTextAnimator()
+ }
+ textAnimator.setTextStyle(
+ fvar = middleFvar,
+ animate = isAnimationEnabled,
+ onAnimationEnd = startAnimPhase2,
+ )
+ outlineAnimator?.setTextStyle(fvar = middleFvar, animate = isAnimationEnabled)
+ innerAnimator?.setTextStyle(fvar = middleFvar, animate = isAnimationEnabled)
+ updateTextBoundsForTextAnimator()
+ }
+
+ fun refreshText() {
+ lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ if (this::textAnimator.isInitialized) {
+ textAnimator.textInterpolator.targetPaint.getTextBounds(
+ text,
+ 0,
+ text.length,
+ targetTextBounds,
+ )
+ }
+ if (layout == null) {
+ requestLayout()
+ } else {
+ textAnimator.updateLayout(layout)
+ outlineAnimator?.updateLayout(layout)
+ innerAnimator?.updateLayout(layout)
+ }
+ }
+
+ private fun isSingleDigit(): Boolean {
+ return id == R.id.HOUR_FIRST_DIGIT ||
+ id == R.id.HOUR_SECOND_DIGIT ||
+ id == R.id.MINUTE_FIRST_DIGIT ||
+ id == R.id.MINUTE_SECOND_DIGIT
+ }
+
+ private fun updateInterpolatedTextBounds(): Rect {
+ val interpolatedTextBounds = Rect()
+ if (textAnimator.animator.animatedFraction != 1.0f && textAnimator.animator.isRunning) {
+ interpolatedTextBounds.left =
+ MathUtils.lerp(
+ prevTextBounds.left,
+ targetTextBounds.left,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.right =
+ MathUtils.lerp(
+ prevTextBounds.right,
+ targetTextBounds.right,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.top =
+ MathUtils.lerp(
+ prevTextBounds.top,
+ targetTextBounds.top,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+
+ interpolatedTextBounds.bottom =
+ MathUtils.lerp(
+ prevTextBounds.bottom,
+ targetTextBounds.bottom,
+ textAnimator.animator.animatedValue as Float,
+ )
+ .toInt()
+ } else {
+ interpolatedTextBounds.set(targetTextBounds)
+ }
+ return interpolatedTextBounds
+ }
+
+ private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
+ val viewWidth = if (isVertical) measuredHeight else measuredWidth
+ when (horizontalAlignment) {
+ HorizontalAlignment.LEFT -> {
+ inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left
+ }
+ HorizontalAlignment.RIGHT -> {
+ inPoint.x =
+ viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt()
+ }
+ HorizontalAlignment.CENTER -> {
+ inPoint.x =
+ (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left
+ }
+ }
+ return inPoint
+ }
+
+ // translation of reference point of text
+ // used for translation when calling textInterpolator
+ fun getLocalTranslation(): Point {
+ val viewHeight = if (isVertical) measuredWidth else measuredHeight
+ val interpolatedTextBounds = updateInterpolatedTextBounds()
+ val localTranslation = Point(0, 0)
+ val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure
+ // get the change from current baseline to expected baseline
+ when (verticalAlignment) {
+ VerticalAlignment.CENTER -> {
+ localTranslation.y =
+ ((viewHeight - interpolatedTextBounds.height()) / 2 -
+ interpolatedTextBounds.top -
+ correctedBaseline)
+ }
+ VerticalAlignment.TOP -> {
+ localTranslation.y =
+ (-interpolatedTextBounds.top + lockScreenPaint.strokeWidth - correctedBaseline)
+ .toInt()
+ }
+ VerticalAlignment.BOTTOM -> {
+ localTranslation.y =
+ viewHeight -
+ interpolatedTextBounds.bottom -
+ lockScreenPaint.strokeWidth.toInt() -
+ correctedBaseline
+ }
+ VerticalAlignment.BASELINE -> {
+ localTranslation.y = -lockScreenPaint.strokeWidth.toInt()
+ }
+ }
+
+ return updateXtranslation(localTranslation, interpolatedTextBounds)
+ }
+
+ override fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?) {
+ this.textStyle = textStyle as FontTextStyle
+ val typefaceName = "fonts/" + textStyle.fontFamily
+ setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName))
+ lockScreenPaint.strokeJoin = Paint.Join.ROUND
+ lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(textStyle.fontVariation)
+ textStyle.fontFeatureSettings?.let {
+ lockScreenPaint.fontFeatureSettings = it
+ fontFeatureSettings = it
+ }
+ typeface = lockScreenPaint.typeface
+ textStyle.lineHeight?.let { lineHeight = it.toInt() }
+ // borderWidth in textStyle and aodStyle is used to draw,
+ // strokeWidth in lockScreenPaint is used to measure and get enough space for the text
+ textStyle.borderWidth?.let { textBorderWidth = parser.convert(it) }
+
+ if (aodStyle != null && aodStyle is FontTextStyle) {
+ this.aodStyle = aodStyle
+ } else {
+ this.aodStyle = textStyle.copy()
+ }
+ this.aodStyle.transitionInterpolator?.let { aodDozingInterpolator = it.interpolator }
+ aodBorderWidth = parser.convert(this.aodStyle.borderWidth ?: DEFAULT_AOD_STROKE_WIDTH)
+ lockScreenPaint.strokeWidth = ceil(max(textBorderWidth, aodBorderWidth))
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ setInterpolatorPaint()
+ recomputeMaxSingleDigitSizes()
+ invalidate()
+ }
+
+ // When constrainedByHeight is on, targetFontSizePx is the constrained height of textView
+ override fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean) {
+ val adjustedFontSizePx = adjustFontSize(targetFontSizePx, constrainedByHeight)
+ val fontSizePx = adjustedFontSizePx * (textStyle.fontSizeScale ?: 1f)
+ aodFontSizePx =
+ adjustedFontSizePx * (aodStyle.fontSizeScale ?: textStyle.fontSizeScale ?: 1f)
+ if (fontSizePx > 0) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+ lockScreenPaint.textSize = textSize
+ lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ targetTextBounds.set(textBounds)
+ }
+ if (!constrainedByHeight) {
+ val lastUnconstrainedHeight = textBounds.height() + lockScreenPaint.strokeWidth * 2
+ fontSizeAdjustFactor = lastUnconstrainedHeight / lastUnconstrainedTextSize
+ }
+ textStyle.borderWidthScale?.let {
+ textBorderWidth = fontSizePx * it
+ if (dozeFraction < 1.0F) {
+ outlineAnimator?.setTextStyle(strokeWidth = textBorderWidth, animate = false)
+ }
+ }
+ aodStyle.borderWidthScale?.let {
+ aodBorderWidth = fontSizePx * it
+ if (dozeFraction > 0.0F) {
+ outlineAnimator?.setTextStyle(strokeWidth = aodBorderWidth, animate = false)
+ }
+ }
+
+ lockScreenPaint.strokeWidth = ceil(max(textBorderWidth, aodBorderWidth))
+ recomputeMaxSingleDigitSizes()
+
+ if (this::textAnimator.isInitialized) {
+ textAnimator.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ }
+ outlineAnimator?.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ innerAnimator?.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ }
+
+ private fun recomputeMaxSingleDigitSizes() {
+ val rectForCalculate = Rect()
+ maxSingleDigitHeight = 0
+ maxSingleDigitWidth = 0
+
+ for (i in 0..9) {
+ lockScreenPaint.getTextBounds(i.toString(), 0, 1, rectForCalculate)
+ maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height())
+ maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width())
+ }
+ maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth.toInt()
+ maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth.toInt()
+ }
+
+ // called without animation, can be used to set the initial state of animator
+ private fun setInterpolatorPaint() {
+ if (this::textAnimator.isInitialized) {
+ // set initial style
+ textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
+ textAnimator.textInterpolator.onTargetPaintModified()
+ textAnimator.setTextStyle(
+ fvar = textStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = textFillColor,
+ animate = false,
+ )
+ }
+
+ if (outlineAnimator != null) {
+ outlineAnimator!!
+ .textInterpolator
+ .targetPaint
+ .set(
+ TextPaint(lockScreenPaint).also {
+ it.style =
+ if (aodStyle.renderType == RenderType.HOLLOW_TEXT)
+ Paint.Style.FILL_AND_STROKE
+ else Paint.Style.STROKE
+ }
+ )
+ outlineAnimator!!.textInterpolator.onTargetPaintModified()
+ outlineAnimator!!.setTextStyle(
+ fvar = aodStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = Color.TRANSPARENT,
+ animate = false,
+ )
+ }
+
+ if (innerAnimator != null) {
+ innerAnimator!!
+ .textInterpolator
+ .targetPaint
+ .set(TextPaint(lockScreenPaint).also { it.style = Paint.Style.FILL })
+ innerAnimator!!.textInterpolator.onTargetPaintModified()
+ innerAnimator!!.setTextStyle(
+ fvar = aodStyle.fontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = Color.WHITE,
+ animate = false,
+ )
+ }
+ }
+
+ /* Called after textAnimator.setTextStyle
+ * textAnimator.setTextStyle will update targetPaint,
+ * and rebase if previous animator is canceled
+ * so basePaint will store the state we transition from
+ * and targetPaint will store the state we transition to
+ */
+ private fun updateTextBoundsForTextAnimator() {
+ textAnimator.textInterpolator.basePaint.getTextBounds(text, 0, text.length, prevTextBounds)
+ textAnimator.textInterpolator.targetPaint.getTextBounds(
+ text,
+ 0,
+ text.length,
+ targetTextBounds,
+ )
+ }
+
+ /*
+ * Adjust text size to adapt to large display / font size
+ * where the text view will be constrained by height
+ */
+ private fun adjustFontSize(targetFontSizePx: Float?, constrainedByHeight: Boolean): Float {
+ return if (constrainedByHeight) {
+ min((targetFontSizePx ?: 0F) / fontSizeAdjustFactor, lastUnconstrainedTextSize)
+ } else {
+ lastUnconstrainedTextSize = targetFontSizePx ?: 1F
+ lastUnconstrainedTextSize
+ }
+ }
+
+ companion object {
+ val DEFAULT_AOD_STROKE_WIDTH = "2dp"
+ val TEXT_OUTLINE_DEFAULT_COLOR = Color.TRANSPARENT
+ val AOD_DEFAULT_COLOR = Color.TRANSPARENT
+ val AOD_OUTLINE_DEFAULT_COLOR = Color.WHITE
+ private val DEFAULT_LIGHT_COLOR = "@android:color/system_accent1_100+0"
+ private val DEFAULT_DARK_COLOR = "@android:color/system_accent2_600+0"
+
+ fun getDefaultColor(assets: AssetLoader, isRegionDark: Boolean) =
+ assets.readColor(if (isRegionDark) DEFAULT_LIGHT_COLOR else DEFAULT_DARK_COLOR)
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
new file mode 100644
index 000000000000..bbd2d3d1f782
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.shared.clocks.view
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.TextStyle
+
+interface SimpleDigitalClockView {
+ var text: String
+ var verticalAlignment: VerticalAlignment
+ var horizontalAlignment: HorizontalAlignment
+ var dozeFraction: Float
+ val textStyle: TextStyle
+ @VisibleForTesting var isAnimationEnabled: Boolean
+
+ fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?)
+
+ fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean = false)
+
+ fun updateColors(assets: AssetLoader, isRegionDark: Boolean)
+
+ fun refreshTime()
+
+ fun animateCharge()
+
+ fun animateDoze(isDozing: Boolean, isAnimated: Boolean)
+}
+
+enum class VerticalAlignment {
+ TOP,
+ BOTTOM,
+ BASELINE, // default
+ CENTER,
+}
+
+enum class HorizontalAlignment {
+ LEFT,
+ RIGHT,
+ CENTER, // default
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 94d3b2ccc9e5..176824fd4c5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -61,6 +61,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserSwitchCallback;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.GlobalSettings;
@@ -70,6 +71,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -96,6 +99,9 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
private UserSwitcherController mUserSwitcherController;
@Mock
private FalsingA11yDelegate mFalsingA11yDelegate;
+ @Captor
+ private ArgumentCaptor<UserSwitchCallback> mUserSwitchCallbackCaptor =
+ ArgumentCaptor.forClass(UserSwitchCallback.class);
private KeyguardSecurityContainer mKeyguardSecurityContainer;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -360,6 +366,18 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
+ public void goingOutOfUserSwitcherRemovesCallback() {
+ // WHEN UserSwitcherViewMode is initialized
+ setupUserSwitcher();
+ verify(mUserSwitcherController).addUserSwitchCallback(mUserSwitchCallbackCaptor.capture());
+
+ // Back to default mode, as SIM PIN would be
+ initMode(MODE_DEFAULT);
+ verify(mUserSwitcherController).removeUserSwitchCallback(
+ mUserSwitchCallbackCaptor.getValue());
+ }
+
+ @Test
public void testOnDensityOrFontScaleChanged() {
setupUserSwitcher();
View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index 944066fa4954..944066fa4954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
index 791a26ed761d..6f43c2068fc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt
@@ -16,9 +16,9 @@
package com.android.systemui.accessibility
-import android.testing.AndroidTestingRunner
import android.util.Size
import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize
import com.google.common.truth.Truth.assertThat
@@ -26,7 +26,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class WindowMagnificationFrameSpecTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index 24f3a29e64ee..24f3a29e64ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 157cccc3d62f..157cccc3d62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
index 0bd00fb0a0e9..0bd00fb0a0e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 662815ee7cbe..662815ee7cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index a8c3af9488f0..a8c3af9488f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 72163e4d7710..72163e4d7710 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 2aa33a176ac9..2aa33a176ac9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 7889b3cd6cc3..7889b3cd6cc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 65825b2444af..2dcbdc80f695 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
@@ -68,10 +69,12 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
@@ -210,6 +213,7 @@ public class AuthControllerTest extends SysuiTestCase {
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false);
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
@@ -462,7 +466,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testShowInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
}
@Test
@@ -679,7 +683,7 @@ public class AuthControllerTest extends SysuiTestCase {
// 2) Client cancels authentication
showDialog(new int[0] /* sensorIds */, true /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
final byte[] credentialAttestation = generateRandomHAT();
@@ -695,7 +699,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- verify(mDialog1).show(any());
+ verify(mDialog1).show(mWindowManager);
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
@@ -703,7 +707,7 @@ public class AuthControllerTest extends SysuiTestCase {
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
// Second dialog should be shown without animation
- verify(mDialog2).show(any());
+ verify(mDialog2).show(mWindowManager);
}
@Test
@@ -990,13 +994,97 @@ public class AuthControllerTest extends SysuiTestCase {
verify(mDialog1, never()).show(any());
}
+ @Test
+ public void testShowDialog_visibleBackgroundUser() {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ WindowManager wm = mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, true /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1).show(wm);
+ }
+
+ @Test
+ public void testShowDialog_invisibleBackgroundUser_defaultWM() {
+ int backgroundUserId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, INVALID_DISPLAY,
+ false /* isVisible */, true /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1).show(mWindowManager);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_noUserManager_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, false /* hasUserManager */, true /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_invalidDisplayId_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, INVALID_DISPLAY,
+ true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
+ public void testShowDialog_visibleBackgroundUser_invalidDisplay_dismissError()
+ throws RemoteException {
+ int backgroundUserId = 1001;
+ int backgroundDisplayId = 1001;
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+ mockBackgroundUser(backgroundUserId, backgroundDisplayId,
+ true /* isVisible */, true /* hasUserManager */, false /* hasDisplay */);
+
+ showDialog(new int[]{1} /* sensorIds */, backgroundUserId /* userId */,
+ false /* credentialAllowed */);
+
+ verify(mDialog1, never()).show(any());
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM),
+ eq(null) /* credentialAttestation */);
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
+ showDialog(sensorIds, 0 /* userId */, credentialAllowed);
+ }
+
+ private void showDialog(int[] sensorIds, int userId, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
sensorIds,
credentialAllowed,
true /* requireConfirmation */,
- 0 /* userId */,
+ userId /* userId */,
0 /* operationId */,
"testPackage",
REQUEST_ID);
@@ -1059,6 +1147,40 @@ public class AuthControllerTest extends SysuiTestCase {
assertTrue(mAuthController.isFaceAuthEnrolled(userId));
}
+ /**
+ * Create mocks related to visible background users.
+ *
+ * @param userId the user id of the background user to mock
+ * @param displayId display id of the background user
+ * @param isVisible whether the background user is a visible background user or not
+ * @param hasUserManager simulate whether the background user's context will return a mock
+ * UserManager instance or null
+ * @param hasDisplay simulate whether the background user's context will return a mock Display
+ * instance or null
+ * @return mock WindowManager instance associated with the background user's display context
+ */
+ private WindowManager mockBackgroundUser(int userId, int displayId, boolean isVisible,
+ boolean hasUserManager, boolean hasDisplay) {
+ Context mockUserContext = mock(Context.class);
+ Context mockDisplayContext = mock(Context.class);
+ UserManager mockUserManager = mock(UserManager.class);
+ Display mockDisplay = mock(Display.class);
+ WindowManager mockDisplayWM = mock(WindowManager.class);
+ doReturn(mockUserContext).when(mContextSpy).createContextAsUser(eq(UserHandle.of(userId)),
+ anyInt());
+ if (hasUserManager) {
+ when(mockUserContext.getSystemService(UserManager.class)).thenReturn(mockUserManager);
+ }
+ when(mockUserManager.isUserVisible()).thenReturn(isVisible);
+ when(mockUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+ if (hasDisplay) {
+ when(mDisplayManager.getDisplay(displayId)).thenReturn(mockDisplay);
+ }
+ doReturn(mockDisplayContext).when(mContextSpy).createDisplayContext(mockDisplay);
+ when(mockDisplayContext.getSystemService(WindowManager.class)).thenReturn(mockDisplayWM);
+ return mockDisplayWM;
+ }
+
private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 9fbe09619ff1..9fbe09619ff1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 4856f156c4c7..4856f156c4c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 30207bb310ba..30207bb310ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 5a3637668cfe..5a3637668cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index dc499cd2fe8d..dc499cd2fe8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
index 08f139c6a3af..08f139c6a3af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
index 4d8fafc2111a..4d8fafc2111a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
index e4c5cd456f03..e4c5cd456f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 55fd3440ea07..55fd3440ea07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
index 0d369a3ea80c..97f2e56e6eec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
@@ -67,7 +67,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.settings.displayTracker
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -127,7 +126,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
private val sceneContainerViewModel by lazy {
kosmos.sceneContainerViewModelFactory
- .create(view, kosmos.displayTracker.defaultDisplayId, {})
+ .create(view) {}
.apply { setTransitionState(transitionState) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 637771790b28..637771790b28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
index 1fe726863f7f..1fe726863f7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index d4d966ad2ef7..2312bbd2d7f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
@@ -102,7 +103,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
widgetId = widgetId,
provider = provider,
rank = rank,
- userSerialNumber = userSerialNumber
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -110,7 +111,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
communalItemRankEntry1,
communalWidgetItemEntry1,
communalItemRankEntry2,
- communalWidgetItemEntry2
+ communalWidgetItemEntry2,
)
}
@@ -129,7 +130,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- userSerialNumber = userSerialNumber
+ userSerialNumber = userSerialNumber,
)
}
@@ -165,7 +166,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
communalItemRankEntry1,
communalWidgetItemEntry1,
communalItemRankEntry2,
- communalWidgetItemEntry2
+ communalWidgetItemEntry2,
)
communalWidgetDao.deleteWidgetById(communalWidgetItemEntry1.widgetId)
@@ -251,6 +252,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = "pk_name/cls_name_4",
itemId = 4L,
userSerialNumber = 0,
+ spanY = 3,
)
assertThat(widgets())
.containsExactly(
@@ -267,6 +269,68 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
@Test
+ fun addWidget_withDifferentSpanY_readsCorrectValuesInDb() =
+ testScope.runTest {
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ // Add widgets with different spanY values
+ communalWidgetDao.addWidget(
+ widgetId = 1,
+ provider = ComponentName("pkg_name", "cls_name_1"),
+ rank = 0,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.FULL.span,
+ )
+ communalWidgetDao.addWidget(
+ widgetId = 2,
+ provider = ComponentName("pkg_name", "cls_name_2"),
+ rank = 1,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.HALF.span,
+ )
+ communalWidgetDao.addWidget(
+ widgetId = 3,
+ provider = ComponentName("pkg_name", "cls_name_3"),
+ rank = 2,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.THIRD.span,
+ )
+
+ // Verify that the widgets have the correct spanY values
+ assertThat(widgets())
+ .containsExactly(
+ CommunalItemRank(uid = 1L, rank = 0),
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pkg_name/cls_name_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.FULL.span,
+ ),
+ CommunalItemRank(uid = 2L, rank = 1),
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = 2,
+ componentName = "pkg_name/cls_name_2",
+ itemId = 2L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.HALF.span,
+ ),
+ CommunalItemRank(uid = 3L, rank = 2),
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = 3,
+ componentName = "pkg_name/cls_name_3",
+ itemId = 3L,
+ userSerialNumber = 0,
+ spanY = CommunalContentSize.THIRD.span,
+ ),
+ )
+ .inOrder()
+ }
+
+ @Test
fun restoreCommunalHubState() =
testScope.runTest {
// Set up db
@@ -288,6 +352,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = fakeWidget.componentName,
itemId = rank.uid,
userSerialNumber = fakeWidget.userSerialNumber,
+ spanY = 3,
)
expected[rank] = widget
}
@@ -343,6 +408,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = widgetInfo1.provider.flattenToString(),
itemId = communalItemRankEntry1.uid,
userSerialNumber = widgetInfo1.userSerialNumber,
+ spanY = 3,
)
val communalWidgetItemEntry2 =
CommunalWidgetItem(
@@ -351,6 +417,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
userSerialNumber = widgetInfo2.userSerialNumber,
+ spanY = 3,
)
val communalWidgetItemEntry3 =
CommunalWidgetItem(
@@ -359,6 +426,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = widgetInfo3.provider.flattenToString(),
itemId = communalItemRankEntry3.uid,
userSerialNumber = widgetInfo3.userSerialNumber,
+ spanY = 3,
)
val fakeState =
CommunalHubState().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index eba395bdb5a3..596db0767867 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -117,6 +117,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[0],
rank = 0,
userSerialNumber = 0,
+ spanY = 3,
)
verify(communalWidgetDao)
.addWidget(
@@ -124,6 +125,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[1],
rank = 1,
userSerialNumber = 0,
+ spanY = 3,
)
verify(communalWidgetDao)
.addWidget(
@@ -131,6 +133,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[2],
rank = 2,
userSerialNumber = 0,
+ spanY = 3,
)
}
@@ -152,6 +155,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = any(),
rank = anyInt(),
userSerialNumber = anyInt(),
+ spanY = anyInt(),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 980a5ec8c494..3d30eccc4572 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -143,7 +143,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
- val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0)
+ val communalWidgetItemEntry =
+ CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0, 3)
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -169,19 +170,15 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
CommunalItemRank(uid = 3L, rank = 3) to
- CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0),
+ CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0, 3),
CommunalItemRank(uid = 4L, rank = 4) to
- CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0),
- )
- fakeProviders.value =
- mapOf(
- 1 to providerInfoA,
- 2 to providerInfoB,
+ CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0, 3),
)
+ fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
// Expect to see only widget 1 and 2
val communalWidgets by collectLastValue(underTest.communalWidgets)
@@ -207,15 +204,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
- )
- fakeProviders.value =
- mapOf(
- 1 to providerInfoA,
- 2 to providerInfoB,
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
)
+ fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
// Expect two widgets
val communalWidgets by collectLastValue(underTest.communalWidgets)
@@ -235,11 +228,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
// Provider info updated for widget 1
- fakeProviders.value =
- mapOf(
- 1 to providerInfoC,
- 2 to providerInfoB,
- )
+ fakeProviders.value = mapOf(1 to providerInfoC, 2 to providerInfoB)
runCurrent()
assertThat(communalWidgets)
@@ -269,7 +258,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -294,7 +283,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -303,7 +292,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -321,7 +310,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -332,7 +321,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -350,7 +339,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(
communalWidgetHost.allocateIdAndBindWidget(
any<ComponentName>(),
- any<UserHandle>()
+ any<UserHandle>(),
)
)
.thenReturn(id)
@@ -650,8 +639,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
eq(newWidgetId),
componentNameCaptor.capture(),
eq(2),
- eq(testUserSerialNumber(workProfile))
+ eq(testUserSerialNumber(workProfile)),
+ anyInt(),
)
+
assertThat(componentNameCaptor.firstValue)
.isEqualTo(ComponentName("pk_name", "fake_widget_2"))
}
@@ -662,9 +653,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
)
// Widget 1 is installed
@@ -707,7 +698,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3)
)
// Widget 1 is pending install
@@ -732,7 +723,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
- ),
+ )
)
// Package for widget 1 finished installing
@@ -749,10 +740,23 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- ),
+ )
)
}
+ @Test
+ fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() =
+ testScope.runTest {
+ val widgetId = 1
+ val newSpanY = 6
+
+ underTest.updateWidgetSpanY(widgetId, newSpanY)
+ runCurrent()
+
+ verify(communalWidgetDao).updateWidgetSpanY(widgetId, newSpanY)
+ verify(backupManager).dataChanged()
+ }
+
private fun setAppWidgetIds(ids: List<Int>) {
whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 58b59ffd8894..755c4ebf5016 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,7 +85,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -102,7 +103,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
}
@Test
@@ -120,7 +122,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
setUpState(
isShadeTouchable = false,
@@ -138,7 +140,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
@@ -156,7 +158,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
setUpState(
isShadeTouchable = false,
@@ -170,7 +174,9 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
}
private fun TestScope.setUpState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
new file mode 100644
index 000000000000..e1946fc7bc6f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ResizeableItemFrameViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.resizeableItemFrameViewModel
+
+ /** Total viewport height of the entire grid */
+ private val viewportHeightPx = 100
+ /** Total amount of vertical padding around the viewport */
+ private val verticalContentPaddingPx = 20f
+
+ private val singleSpanGrid =
+ GridLayout(
+ verticalItemSpacingPx = 10f,
+ verticalContentPaddingPx = verticalContentPaddingPx,
+ viewportHeightPx = viewportHeightPx,
+ maxItemSpan = 1,
+ minItemSpan = 1,
+ currentSpan = 1,
+ currentRow = 0,
+ )
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun testDefaultState() {
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.offset).isEqualTo(0f)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.offset).isEqualTo(0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testSingleSpanGrid() =
+ testScope.runTest(timeout = Duration.INFINITE) {
+ updateGridLayout(singleSpanGrid)
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ /**
+ * Verifies element in first row which is already at the minimum size can only be expanded
+ * downwards.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInFirstRow_sizeSingleSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+ }
+
+ /**
+ * Verifies element in second row which is already at the minimum size can only be expanded
+ * upwards.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInSecondRow_sizeSingleSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ /**
+ * Verifies element in first row which is already at full size (2 span) can only be shrunk from
+ * the bottom.
+ */
+ @Test
+ fun testTwoSpanGrid_elementInFirstRow_sizeTwoSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+ }
+
+ /**
+ * Verifies element in a middle row at minimum size can be expanded from either top or bottom.
+ */
+ @Test
+ fun testThreeSpanGrid_elementInMiddleRow_sizeOneSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
+ }
+
+ @Test
+ fun testThreeSpanGrid_elementInTopRow_sizeOneSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f, 2 to 60f)
+ }
+
+ @Test
+ fun testSixSpanGrid_minSpanThree_itemInThirdRow_sizeThreeSpans() =
+ testScope.runTest {
+ updateGridLayout(
+ singleSpanGrid.copy(
+ maxItemSpan = 6,
+ currentRow = 3,
+ currentSpan = 3,
+ minItemSpan = 3,
+ )
+ )
+
+ val topState = underTest.topDragState
+ assertThat(topState.currentValue).isEqualTo(0)
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -3 to -45f)
+
+ val bottomState = underTest.bottomDragState
+ assertThat(bottomState.currentValue).isEqualTo(0)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testTwoSpanGrid_elementMovesFromFirstRowToSecondRow() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ val topState = underTest.topDragState
+ val bottomState = underTest.bottomDragState
+
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
+
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1))
+
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
+ }
+
+ @Test
+ fun testTwoSpanGrid_expandElementFromBottom() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(45f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testThreeSpanGrid_expandMiddleElementUpwards() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1))
+
+ assertThat(resizeInfo).isNull()
+ underTest.topDragState.anchoredDrag { dragTo(-30f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
+ }
+
+ @Test
+ fun testThreeSpanGrid_expandTopElementDownBy2Spans() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(60f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(2, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testTwoSpanGrid_shrinkElementFromBottom() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2))
+
+ assertThat(resizeInfo).isNull()
+ underTest.bottomDragState.anchoredDrag { dragTo(-45f) }
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_maxSpanSmallerThanMinSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 3))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_minSpanOfZero() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 0))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_maxSpanOfZero() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 0, minItemSpan = 0))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testIllegalState_currentRowNotMultipleOfMinSpan() =
+ testScope.runTest {
+ updateGridLayout(singleSpanGrid.copy(maxItemSpan = 6, minItemSpan = 3, currentSpan = 2))
+ }
+
+ private fun TestScope.updateGridLayout(gridLayout: GridLayout) {
+ underTest.setGridLayoutInfo(
+ gridLayout.verticalItemSpacingPx,
+ gridLayout.verticalContentPaddingPx,
+ gridLayout.viewportHeightPx,
+ gridLayout.maxItemSpan,
+ gridLayout.minItemSpan,
+ gridLayout.currentRow,
+ gridLayout.currentSpan,
+ )
+ runCurrent()
+ }
+
+ private fun DraggableAnchors<Int>.toList() = buildList {
+ for (index in 0 until this@toList.size) {
+ add(anchorAt(index) to positionAt(index))
+ }
+ }
+
+ private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) {
+ val globalWriteObserverHandle =
+ Snapshot.registerGlobalWriteObserver {
+ // This is normally done by the compose runtime.
+ Snapshot.sendApplyNotifications()
+ }
+
+ try {
+ testScope.runTest(testBody = testBody)
+ } finally {
+ globalWriteObserverHandle.dispose()
+ }
+ }
+
+ private data class GridLayout(
+ val verticalItemSpacingPx: Float,
+ val verticalContentPaddingPx: Float,
+ val viewportHeightPx: Int,
+ val maxItemSpan: Int,
+ val minItemSpan: Int,
+ val currentRow: Int,
+ val currentSpan: Int,
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 40b2a0864a8c..40b2a0864a8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 6c6de61c638a..6c6de61c638a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index a3314e8900ce..a3314e8900ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
index c2c94a88603a..c2c94a88603a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
index 98e09474d5f2..98e09474d5f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index d7fe263df581..dd8370231ef0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -71,7 +71,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@get:Rule val mockitoRule = MockitoJUnit.rule()
private var toastContent = ""
- private val timeoutMillis = 3500L
+ private val timeoutMillis = 5000L
@Before
fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 4a80d7242e03..4a80d7242e03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 8f9e23824809..8b1341114c68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -145,7 +145,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
fakeInputManager.addPhysicalKeyboard(
PHYSICAL_NOT_FULL_KEYBOARD_ID,
- isFullKeyboard = false
+ isFullKeyboard = false,
)
assertThat(isKeyboardConnected).isFalse()
@@ -223,7 +223,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
backlightListenerCaptor.value.onBacklightChanged(
current = 1,
max = 5,
- triggeredByKeyPress = false
+ triggeredByKeyPress = false,
)
assertThat(backlight).isNull()
}
@@ -239,7 +239,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
backlightListenerCaptor.value.onBacklightChanged(
current = 1,
max = 5,
- triggeredByKeyPress = true
+ triggeredByKeyPress = true,
)
assertThat(backlight).isNotNull()
}
@@ -318,15 +318,75 @@ class KeyboardRepositoryTest : SysuiTestCase() {
}
}
+ @Test
+ fun connectedKeyboards_emitsAllKeyboards() {
+ testScope.runTest {
+ val firstKeyboard = Keyboard(vendorId = 1, productId = 1)
+ val secondKeyboard = Keyboard(vendorId = 2, productId = 2)
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_FULL_KEYBOARD_ID,
+ vendorId = firstKeyboard.vendorId,
+ productId = firstKeyboard.productId,
+ )
+ assertThat(keyboards)
+ .containsExactly(Keyboard(firstKeyboard.vendorId, firstKeyboard.productId))
+
+ fakeInputManager.addPhysicalKeyboard(
+ ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
+ vendorId = secondKeyboard.vendorId,
+ productId = secondKeyboard.productId,
+ )
+ assertThat(keyboards)
+ .containsExactly(
+ Keyboard(firstKeyboard.vendorId, firstKeyboard.productId),
+ Keyboard(secondKeyboard.vendorId, secondKeyboard.productId),
+ )
+ }
+ }
+
+ @Test
+ fun connectedKeyboards_emitsOnlyFullPhysicalKeyboards() {
+ testScope.runTest {
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_NOT_FULL_KEYBOARD_ID,
+ isFullKeyboard = false,
+ )
+
+ assertThat(keyboards).hasSize(1)
+ }
+ }
+
+ @Test
+ fun connectedKeyboards_emitsOnlyConnectedKeyboards() {
+ testScope.runTest {
+ captureDeviceListener()
+ val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)
+
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+
+ assertThat(keyboards).hasSize(1)
+ }
+ }
+
private fun KeyboardBacklightListener.onBacklightChanged(
current: Int,
max: Int,
- triggeredByKeyPress: Boolean = true
+ triggeredByKeyPress: Boolean = true,
) {
onKeyboardBacklightChanged(
/* deviceId= */ 0,
TestBacklightState(current, max),
- triggeredByKeyPress
+ triggeredByKeyPress,
)
}
@@ -343,7 +403,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
private class TestBacklightState(
private val brightnessLevel: Int,
- private val maxBrightnessLevel: Int
+ private val maxBrightnessLevel: Int,
) : KeyboardBacklightState() {
override fun getBrightnessLevel() = brightnessLevel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 6e883c24b5d2..6e883c24b5d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 57c8b444b922..57c8b444b922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui
index f8e2f47f939a..f8e2f47f939a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
index 1e9db6466de6..1e9db6466de6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
index 5a597fe8e920..5a597fe8e920 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 073ed61a949b..b6ec6a67b212 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -21,16 +21,23 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_EFFECT
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository.RevealAnimatorRequest
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.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
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
@@ -52,6 +59,8 @@ class LightRevealScrimInteractorTest : SysuiTestCase() {
private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakePowerRepository = kosmos.fakePowerRepository
+
private val underTest = kosmos.lightRevealScrimInteractor
private val reveal1 =
@@ -107,4 +116,50 @@ class LightRevealScrimInteractorTest : SysuiTestCase() {
runCurrent()
assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1, reveal2), values)
}
+
+ @Test
+ fun transitionToAod_folding_doesNotAnimateTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.FOLD)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(RevealAnimatorRequest(reveal = false, duration = 0))
+ }
+
+ @Test
+ fun transitionToAod_powerButton_animatesTheScrim() =
+ kosmos.testScope.runTest {
+ updateWakefulness(goToSleepReason = WakeSleepReason.POWER_BUTTON)
+ runCurrent()
+
+ // Transition to AOD
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
+ )
+ runCurrent()
+
+ assertThat(fakeLightRevealScrimRepository.revealAnimatorRequests.last())
+ .isEqualTo(
+ RevealAnimatorRequest(
+ reveal = false,
+ duration = DEFAULT_REVEAL_DURATION
+ )
+ )
+ }
+
+ private fun updateWakefulness(goToSleepReason: WakeSleepReason) {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = goToSleepReason,
+ powerButtonLaunchGestureTriggered = false
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 43c7ed6a769d..43c7ed6a769d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index fb1bf281715d..6397979d9627 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -303,7 +303,8 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
// Top edge is not applicable in dual shade, as well as two-finger swipe.
assertThat(downDestination).isNull()
} else {
- assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
+ assertThat(downDestination)
+ .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
assertThat(downDestination?.transitionKey).isNull()
}
@@ -320,7 +321,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
else -> {
assertThat(downFromTopRightDestination)
- .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
+ .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
assertThat(downFromTopRightDestination?.transitionKey).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index 030b1726bb73..030b1726bb73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
index dfd964f0eaa7..dfd964f0eaa7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index 9c4c862cccf2..9c4c862cccf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/table/TableLogBufferTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index 414974cc2941..414974cc2941 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 5bedc134a1ee..5bedc134a1ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
index 84ec1a5677aa..77be8c718b14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediarouter.data.repository
import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -28,9 +29,11 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class MediaRouterRepositoryTest : SysuiTestCase() {
val kosmos = Kosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
index acd69af2736a..da166408b926 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
@@ -26,10 +26,11 @@ import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
AbstractQSFragmentComposeViewModelTest() {
@@ -75,7 +76,7 @@ class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
companion object {
private const val EXPANSION = 0.3f
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun createTestData(): List<TestData> {
return statusBarStates.flatMap { statusBarState ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index 5c47f552e400..47fae9f0629f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -106,7 +106,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.QuickSettings))
+ .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
}
@Test
@@ -118,7 +118,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
deleted file mode 100644
index efde1ecec512..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilterTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.scene.ui.viewmodel
-
-import android.graphics.Region
-import android.view.setSystemGestureExclusionRegion
-import androidx.compose.ui.geometry.Offset
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.settings.displayTracker
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SceneContainerGestureFilterTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val displayId = kosmos.displayTracker.defaultDisplayId
-
- private val underTest = kosmos.sceneContainerGestureFilterFactory.create(displayId)
- private val activationJob = Job()
-
- @Test
- fun shouldFilterGesture_whenNoRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, null)
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenOutsideRegion_returnsFalse() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(300f, 100f))).isFalse()
- }
-
- @Test
- fun shouldFilterGesture_whenInsideRegion_returnsTrue() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- assertThat(underTest.shouldFilterGesture(Offset(100f, 100f))).isTrue()
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_beforeActivation_throws() =
- testScope.runTest {
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- @Test(expected = IllegalStateException::class)
- fun shouldFilterGesture_afterCancellation_throws() =
- testScope.runTest {
- activate()
- setSystemGestureExclusionRegion(displayId, Region(0, 0, 200, 200))
- runCurrent()
-
- cancel()
-
- underTest.shouldFilterGesture(Offset(100f, 100f))
- }
-
- private fun TestScope.activate() {
- underTest.activateIn(testScope, activationJob)
- runCurrent()
- }
-
- private fun TestScope.cancel() {
- activationJob.cancel()
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
index 664315d19494..ca9500b5d74b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
@@ -51,7 +51,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -134,7 +134,7 @@ class SceneContainerHapticsViewModelTest() : SysuiTestCase() {
runCurrent()
// THEN the view does not play a haptic feedback constant
- verifyZeroInteractions(view)
+ verifyNoMoreInteractions(view)
}
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE)
@@ -202,7 +202,7 @@ class SceneContainerHapticsViewModelTest() : SysuiTestCase() {
runCurrent()
// THEN the view does not play a haptic feedback constant
- verifyZeroInteractions(view)
+ verifyNoMoreInteractions(view)
}
private fun createTransitionState(from: SceneKey, to: ContentKey) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index a37f511cef69..b632a8aaad46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -39,7 +39,6 @@ import com.android.systemui.scene.sceneContainerViewModelFactory
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -82,7 +81,6 @@ class SceneContainerViewModelTest : SysuiTestCase() {
underTest =
kosmos.sceneContainerViewModelFactory.create(
view,
- kosmos.displayTracker.defaultDisplayId,
{ motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
@@ -178,8 +176,8 @@ class SceneContainerViewModelTest : SysuiTestCase() {
sceneContainerConfig.sceneKeys
.filter { it != currentScene }
.filter {
- // Moving to the Communal scene is not currently falsing protected.
- it != Scenes.Communal
+ // Moving to the Communal and Dream scene is not currently falsing protected.
+ it != Scenes.Communal && it != Scenes.Dream
}
.forEach { toScene ->
assertWithMessage("Protected scene $toScene not properly protected")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 15d68813808c..fcb366bfd8ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -50,6 +51,7 @@ import com.android.systemui.shade.domain.startable.shadeStartable
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -248,6 +250,27 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Communal))
}
+ @Test
+ fun upTransitionSceneKey_neverGoesBackToShadeScene() =
+ testScope.runTest {
+ val actions by collectValues(underTest.actions)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ actions.forEachIndexed { index, map ->
+ assertWithMessage(
+ "Actions on index $index is incorrectly mapping back to the Shade scene!"
+ )
+ .that((map[Swipe.Up] as? UserActionResult.ChangeScene)?.toScene)
+ .isNotEqualTo(Scenes.Shade)
+ }
+ }
+
private fun TestScope.setDeviceEntered(isEntered: Boolean) {
if (isEntered) {
// Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 523a89ad5740..5b0b59de47c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -71,7 +71,11 @@ import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -90,6 +94,10 @@ import com.android.systemui.util.time.FakeSystemClock;
import com.google.android.collect.Lists;
+import dagger.Lazy;
+
+import kotlinx.coroutines.flow.StateFlow;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -152,6 +160,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ @Mock
+ private DeviceUnlockedInteractor mDeviceUnlockedInteractor;
+ @Mock
+ private StateFlow<DeviceUnlockStatus> mDeviceUnlockStatusStateFlow;
private UserInfo mCurrentUser;
private UserInfo mSecondaryUser;
@@ -238,6 +252,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
mLockscreenUserManager.setUpWithPresenter(mPresenter);
+ when(mDeviceUnlockedInteractor.getDeviceUnlockStatus())
+ .thenReturn(mDeviceUnlockStatusStateFlow);
+
mBackgroundExecutor.runAllReady();
}
@@ -493,7 +510,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
- public void testUpdateIsPublicMode() {
+ @DisableSceneContainer
+ public void testUpdateIsPublicMode_sceneContainerDisabled() {
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
when(mKeyguardStateController.isShowing()).thenReturn(false);
@@ -527,6 +545,57 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mBackgroundExecutor.runAllReady();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
+
+ verify(mDeviceUnlockedInteractorLazy, never()).get();
+ }
+
+ @Test
+ @EnableSceneContainer
+ public void testUpdateIsPublicMode_sceneContainerEnabled() {
+ when(mDeviceUnlockedInteractorLazy.get()).thenReturn(mDeviceUnlockedInteractor);
+
+ // device is unlocked
+ when(mDeviceUnlockStatusStateFlow.getValue()).thenReturn(new DeviceUnlockStatus(
+ /* isUnlocked = */ true,
+ /* deviceUnlockSource = */ null
+ ));
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // device is not unlocked
+ when(mDeviceUnlockStatusStateFlow.getValue()).thenReturn(new DeviceUnlockStatus(
+ /* isUnlocked = */ false,
+ /* deviceUnlockSource = */ null
+ ));
+
+ // Calling again with device now not unlocked makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ mBackgroundExecutor.runAllReady();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
}
@Test
@@ -972,7 +1041,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mSettings,
mock(DumpManager.class),
mock(LockPatternUtils.class),
- mFakeFeatureFlags);
+ mFakeFeatureFlags,
+ mDeviceUnlockedInteractorLazy
+ );
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index beba16229c12..ea5c29ef30aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,12 +29,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.compose.animation.scene.ObservableTransitionState;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
@@ -67,9 +69,6 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -79,6 +78,9 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@@ -517,6 +519,32 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ public void testNotLockscreenInGoneTransition_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.RUNNING), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
+ mTestScope, new TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.FINISHED), /* validateStep = */ false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1));
+ }
+
+ @Test
public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
// GIVEN animation is playing
setPanelCollapsing(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 28857a08c2bd..34f46088ad79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -208,8 +208,8 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(footerVisible).isTrue()
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenHistoryDisabled_leadsToSettingsPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -222,8 +222,8 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(onClick?.backStack).isEmpty()
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenHistoryEnabled_leadsToHistoryPage() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -237,8 +237,8 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
.containsExactly(Settings.ACTION_NOTIFICATION_SETTINGS)
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenOneModeHidingNotifications_leadsToModeSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
@@ -263,8 +263,8 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
.containsExactly(Settings.ACTION_ZEN_MODE_SETTINGS)
}
- @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
@Test
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun onClick_whenMultipleModesHidingNotifications_leadsToGeneralModesSettings() =
testScope.runTest {
val onClick by collectLastValue(underTest.onClick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f40bfbdeb54b..8d678ef00b4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -506,7 +506,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas
@EnableSceneContainer
fun pinnedHeadsUpRows_filtersForPinnedItems() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN there are no pinned rows
val rows =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index a0f64314098c..9d93a9c7fe0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -61,7 +61,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
interactor,
kosmos.testDispatcher,
mockDialogDelegate,
- mockDialogEventLogger
+ mockDialogEventLogger,
)
@Test
@@ -97,7 +97,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
assertThat(tiles?.size).isEqualTo(3)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(1)!!) {
@@ -323,10 +323,10 @@ class ModesDialogViewModelTest : SysuiTestCase() {
assertThat(tiles!!).hasSize(6)
assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
- assertThat(tiles!![2].subtext).isEqualTo("Set up")
+ assertThat(tiles!![2].subtext).isEqualTo("Not set")
assertThat(tiles!![3].subtext).isEqualTo("Off")
assertThat(tiles!![4].subtext).isEqualTo("On")
- assertThat(tiles!![5].subtext).isEqualTo("Set up")
+ assertThat(tiles!![5].subtext).isEqualTo("Not set")
}
@Test
@@ -387,7 +387,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
}
with(tiles?.elementAt(2)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
with(tiles?.elementAt(3)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
@@ -399,7 +399,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
}
with(tiles?.elementAt(5)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
- assertThat(this.subtextDescription).isEqualTo("Set up")
+ assertThat(this.subtextDescription).isEqualTo("Not set")
}
// All tiles have the same long click info
@@ -451,7 +451,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
.setName("Active without manual")
.setActive(true)
.setManualInvocationAllowed(false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -492,7 +492,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
.setId("ID")
.setName("Disabled by other")
.setEnabled(false, /* byUser= */ false)
- .build(),
+ .build()
)
)
runCurrent()
@@ -500,7 +500,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
assertThat(tiles?.size).isEqualTo(1)
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
// Click the tile
@@ -519,7 +519,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
// Check that nothing happened to the tile
with(tiles?.elementAt(0)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
new file mode 100644
index 000000000000..7ce421a5aa62
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.days
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val dialogTimeoutDuration = 3.seconds
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper()
+class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {
+
+ private val kosmos: Kosmos = testKosmos()
+
+ private lateinit var underTest: VolumeDialogVisibilityInteractor
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.volumeDialogVisibilityInteractor
+ }
+
+ @Test
+ fun testShowRequest_visible() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(
+ VolumeDialogVisibilityModel.Visible(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDismissRequest_dismissed() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ fakeVolumeDialogController.onDismissRequested(Events.DISMISS_REASON_SCREEN_OFF)
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(
+ VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_SCREEN_OFF)
+ )
+ }
+ }
+
+ @Test
+ fun testTimeout_dismissed() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ underTest.resetDismissTimeout()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ advanceTimeBy(1.days)
+
+ assertThat(visibilityModel!!)
+ .isEqualTo(VolumeDialogVisibilityModel.Dismissed(Events.DISMISS_REASON_TIMEOUT))
+ }
+ }
+
+ @Test
+ fun testResetTimeoutInterruptsEvents() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ underTest.resetDismissTimeout()
+ val visibilityModel by collectLastValue(underTest.dialogVisibility)
+ fakeVolumeDialogController.onShowRequested(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ runCurrent()
+
+ advanceTimeBy(dialogTimeoutDuration / 2)
+ underTest.resetDismissTimeout()
+ advanceTimeBy(dialogTimeoutDuration / 2)
+ underTest.resetDismissTimeout()
+ advanceTimeBy(dialogTimeoutDuration / 2)
+
+ assertThat(visibilityModel)
+ .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
index e1be6b008903..d2688a8fc146 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -53,7 +53,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.wm.shell.desktopmode.DesktopMode
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.onehanded.OneHanded
import com.android.wm.shell.onehanded.OneHandedEventCallback
import com.android.wm.shell.onehanded.OneHandedTransitionCallback
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
new file mode 100644
index 000000000000..9f3d07500910
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_home_icon.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M240,760L360,760L360,520L600,520L600,760L720,760L720,400L480,220L240,400L240,760ZM160,840L160,360L480,120L800,360L800,840L520,840L520,600L440,600L440,840L160,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
new file mode 100644
index 000000000000..113908aa8bca
--- /dev/null
+++ b/packages/SystemUI/res/drawable/touchpad_tutorial_recents_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L200,160Q233,160 256.5,183.5Q280,207 280,240L280,720Q280,753 256.5,776.5Q233,800 200,800L120,800ZM120,721L200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L120,239Q120,239 120,239Q120,239 120,239L120,721Q120,721 120,721Q120,721 120,721ZM440,800Q407,800 383.5,776.5Q360,753 360,720L360,240Q360,207 383.5,183.5Q407,160 440,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L440,800ZM440,721L840,721Q840,721 840,721Q840,721 840,721L840,239Q840,239 840,239Q840,239 840,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721ZM200,721Q200,721 200,721Q200,721 200,721L200,239Q200,239 200,239Q200,239 200,239L200,239Q200,239 200,239Q200,239 200,239L200,721Q200,721 200,721Q200,721 200,721ZM440,721Q440,721 440,721Q440,721 440,721L440,239Q440,239 440,239Q440,239 440,239L440,239Q440,239 440,239Q440,239 440,239L440,721Q440,721 440,721Q440,721 440,721Z" />
+</vector>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index e9dd039f38c2..7bd4ca8b940b 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -64,4 +64,9 @@
<item name="android:windowLightNavigationBar">false</item>
</style>
+ <style name="ContextualEduDialog" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+ <!-- To make the dialog wrap to content when the education text is short -->
+ <item name="windowMinWidthMajor">0%</item>
+ <item name="windowMinWidthMinor">0%</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c76b35f0cf9b..96a85d78e2b5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1120,7 +1120,7 @@
<string name="zen_mode_off">Off</string>
<!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
- <string name="zen_mode_set_up">Set up</string>
+ <string name="zen_mode_set_up">Not set</string>
<!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
<string name="zen_mode_no_manual_invocation">Manage in settings</string>
@@ -1827,6 +1827,12 @@
<!-- Name of the alarm status bar icon. -->
<string name="status_bar_alarm">Alarm</string>
+ <!-- Format string for the content description of the icon that indicates that a Mode is on.
+ For example, if the mode name is Bedtime, this will be "Bedtime is on". This content
+ description will be associated to the mode icon in status bar, smartspace, and everyone else
+ where it might be displayed without text. [CHAR LIMIT=NONE] -->
+ <string name="active_mode_content_description"><xliff:g id="modeName" example="Do Not Disturb">%1$s</xliff:g> is on</string>
+
<!-- Wallet strings -->
<!-- Wallet empty state, title [CHAR LIMIT=32] -->
<string name="wallet_title">Wallet</string>
@@ -3746,9 +3752,9 @@
<!-- TOUCHPAD TUTORIAL-->
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
- <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
+ <string name="touchpad_tutorial_back_gesture_button">Go back</string>
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
- <string name="touchpad_tutorial_home_gesture_button">Home gesture</string>
+ <string name="touchpad_tutorial_home_gesture_button">Go home</string>
<!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
@@ -3757,26 +3763,25 @@
<!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_action_title">Go back</string>
<!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
-Action + ESC for this.</string>
+ <string name="touchpad_back_gesture_guidance">Swipe left or right using three fingers on your touchpad</string>
<!-- Screen title after back gesture was done successfully [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_success_title">Great job!</string>
+ <string name="touchpad_back_gesture_success_title">Nice!</string>
<!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
<!-- HOME GESTURE -->
<!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_home_gesture_action_title">Go home</string>
<!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+ <string name="touchpad_home_gesture_guidance">Swipe up with three fingers on your touchpad</string>
<!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_success_title">Nice!</string>
+ <string name="touchpad_home_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string>
+ <string name="touchpad_home_gesture_success_body">You completed the go home gesture</string>
<!-- RECENT APPS GESTURE -->
<!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_action_title">View recent apps</string>
<!-- Touchpad recent apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad.</string>
+ <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad</string>
<!-- Screen title after recent apps gesture was done successfully [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] -->
@@ -3784,13 +3789,13 @@ Action + ESC for this.</string>
<!-- KEYBOARD TUTORIAL-->
<!-- Action key tutorial title [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_title">Action key</string>
+ <string name="tutorial_action_key_title">View all apps</string>
<!-- Action key tutorial guidance[CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string>
+ <string name="tutorial_action_key_guidance">Press the action key on your keyboard</string>
<!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_success_title">Congratulations!</string>
+ <string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
- <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string>
+ <string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b34d6e4067b6..94b0b5f67934 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -540,6 +540,7 @@
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
<style name="volume_dialog_theme" parent="Theme.SystemUI">
<item name="android:windowIsFloating">true</item>
+ <item name="android:showWhenLocked">true</item>
</style>
<style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
@@ -1721,7 +1722,7 @@
<item name="android:windowLightNavigationBar">true</item>
</style>
- <style name="ContextualEduDialog" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+ <style name="ContextualEduDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
<!-- To make the dialog wrap to content when the education text is short -->
<item name="windowMinWidthMajor">0%</item>
<item name="windowMinWidthMinor">0%</item>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json
new file mode 100644
index 000000000000..c3fb8d4d5ab6
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/4.json
@@ -0,0 +1,88 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "a49f2f7d25cf12d1baf9a3a3e6243b64",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1, `span_y` INTEGER NOT NULL DEFAULT 3)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ },
+ {
+ "fieldPath": "spanY",
+ "columnName": "span_y",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "3"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a49f2f7d25cf12d1baf9a3a3e6243b64')"
+ ]
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f05cbf422707..2d27f1c0ca73 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1067,6 +1067,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
@Override
public void onDestroy() {
+ mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mView);
constraintSet.clear(mUserSwitcherViewGroup.getId());
@@ -1075,6 +1077,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
mView.removeView(mUserSwitcherViewGroup);
mView.removeView(mUserSwitcher);
+ mUserSwitcher = null;
+ mUserSwitcherViewGroup = null;
}
private void findLargeUserIcon(int userId, Consumer<Drawable> consumer) {
@@ -1102,6 +1106,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
return;
}
+ if (mUserSwitcherViewGroup == null) {
+ return;
+ }
+
mUserSwitcherViewGroup.setAlpha(0f);
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -1110,14 +1118,18 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mUserSwitcherViewGroup.setAlpha(1f);
- mUserSwitcherViewGroup.setTranslationY(0f);
+ if (mUserSwitcherViewGroup != null) {
+ mUserSwitcherViewGroup.setAlpha(1f);
+ mUserSwitcherViewGroup.setTranslationY(0f);
+ }
}
});
animator.addUpdateListener(animation -> {
- float value = (float) animation.getAnimatedValue();
- mUserSwitcherViewGroup.setAlpha(value);
- mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ if (mUserSwitcherViewGroup != null) {
+ float value = (float) animation.getAnimatedValue();
+ mUserSwitcherViewGroup.setAlpha(value);
+ mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ }
});
animator.start();
}
@@ -1148,6 +1160,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
Log.e(TAG, "Current user in user switcher is null.");
return;
}
+ if (mUserSwitcher == null) {
+ Log.w(TAG, "User switcher is not inflated, cannot setupUserSwitcher");
+ return;
+ }
final String currentUserName = mUserSwitcherController.getCurrentUserName();
findLargeUserIcon(currentUser.info.id,
(Drawable userIcon) -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 22130f827841..8e01e04c155e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,6 +114,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
+import com.android.keyguard.logging.SimLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -285,6 +286,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Context mContext;
private final UserTracker mUserTracker;
private final KeyguardUpdateMonitorLogger mLogger;
+ private final SimLogger mSimLogger;
private final boolean mIsSystemUser;
private final Provider<JavaAdapter> mJavaAdapter;
private final Provider<SceneInteractor> mSceneInteractor;
@@ -582,14 +584,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleSimSubscriptionInfoChanged() {
Assert.isMainThread();
- mLogger.v("onSubscriptionInfoChanged()");
+ mSimLogger.v("onSubscriptionInfoChanged()");
List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
if (!subscriptionInfos.isEmpty()) {
for (SubscriptionInfo subInfo : subscriptionInfos) {
- mLogger.logSubInfo(subInfo);
+ mSimLogger.logSubInfo(subInfo);
}
} else {
- mLogger.v("onSubscriptionInfoChanged: list is null");
+ mSimLogger.v("onSubscriptionInfoChanged: list is null");
}
// Hack level over 9000: Because the subscription id is not yet valid when we see the
@@ -612,7 +614,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
while (iter.hasNext()) {
Map.Entry<Integer, SimData> simData = iter.next();
if (!activeSubIds.contains(simData.getKey())) {
- mLogger.logInvalidSubId(simData.getKey());
+ mSimLogger.logInvalidSubId(simData.getKey());
iter.remove();
SimData data = simData.getValue();
@@ -1700,7 +1702,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
return;
}
- mLogger.logSimStateFromIntent(action,
+ mSimLogger.logSimStateFromIntent(action,
intent.getStringExtra(Intent.EXTRA_SIM_STATE),
args.slotId,
args.subId);
@@ -1720,7 +1722,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- mLogger.logServiceStateIntent(action, serviceState, subId);
+ mSimLogger.logServiceStateIntent(action, serviceState, subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
@@ -2154,6 +2156,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
LatencyTracker latencyTracker,
ActiveUnlockConfig activeUnlockConfiguration,
KeyguardUpdateMonitorLogger logger,
+ SimLogger simLogger,
UiEventLogger uiEventLogger,
// This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
Provider<SessionTracker> sessionTrackerProvider,
@@ -2196,6 +2199,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
mLogger = logger;
+ mSimLogger = simLogger;
mUiEventLogger = uiEventLogger;
mSessionTrackerProvider = sessionTrackerProvider;
mTrustManager = trustManager;
@@ -3369,36 +3373,39 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * Removes all valid subscription info from the map for the given slotId.
+ */
+ private void invalidateSlot(int slotId) {
+ Iterator<Map.Entry<Integer, SimData>> iter = mSimDatas.entrySet().iterator();
+ while (iter.hasNext()) {
+ SimData data = iter.next().getValue();
+ if (data.slotId == slotId && SubscriptionManager.isValidSubscriptionId(data.subId)) {
+ mSimLogger.logInvalidSubId(data.subId);
+ iter.remove();
+ }
+ }
+ }
+
+ /**
* Handle {@link #MSG_SIM_STATE_CHANGE}
*/
@VisibleForTesting
void handleSimStateChange(int subId, int slotId, int state) {
Assert.isMainThread();
- mLogger.logSimState(subId, slotId, state);
+ mSimLogger.logSimState(subId, slotId, state);
- boolean becameAbsent = false;
+ boolean becameAbsent = ABSENT_SIM_STATE_LIST.contains(state);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- mLogger.w("invalid subId in handleSimStateChange()");
+ mSimLogger.w("invalid subId in handleSimStateChange()");
/* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
* handleServiceStateChange() handle other case */
- if (state == TelephonyManager.SIM_STATE_ABSENT) {
- updateTelephonyCapable(true);
- // Even though the subscription is not valid anymore, we need to notify that the
- // SIM card was removed so we can update the UI.
- becameAbsent = true;
- for (SimData data : mSimDatas.values()) {
- // Set the SIM state of all SimData associated with that slot to ABSENT se we
- // do not move back into PIN/PUK locked and not detect the change below.
- if (data.slotId == slotId) {
- data.simState = TelephonyManager.SIM_STATE_ABSENT;
- }
- }
- } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
+ if (state == TelephonyManager.SIM_STATE_ABSENT
+ || state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
updateTelephonyCapable(true);
}
- }
- becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state);
+ invalidateSlot(slotId);
+ }
// TODO(b/327476182): Preserve SIM_STATE_CARD_IO_ERROR sims in a separate data source.
SimData data = mSimDatas.get(subId);
@@ -3428,10 +3435,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@VisibleForTesting
void handleServiceStateChange(int subId, ServiceState serviceState) {
- mLogger.logServiceStateChange(subId, serviceState);
+ mSimLogger.logServiceStateChange(subId, serviceState);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- mLogger.w("invalid subId in handleServiceStateChange()");
+ mSimLogger.w("invalid subId in handleServiceStateChange()");
return;
} else {
updateTelephonyCapable(true);
@@ -3711,7 +3718,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@MainThread
public void reportSimUnlocked(int subId) {
- mLogger.logSimUnlocked(subId);
+ mSimLogger.logSimUnlocked(subId);
handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
}
@@ -3870,6 +3877,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean refreshSimState(int subId, int slotId) {
int state = mTelephonyManager.getSimState(slotId);
SimData data = mSimDatas.get(subId);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ invalidateSlot(slotId);
+ }
+
final boolean changed;
if (data == null) {
data = new SimData(state, slotId, subId);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 0b58f06e0d5d..12fc3c262367 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -20,8 +20,6 @@ import android.content.Intent
import android.hardware.biometrics.BiometricConstants.LockoutMode
import android.hardware.biometrics.BiometricSourceType
import android.os.PowerManager
-import android.telephony.ServiceState
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyManager
@@ -34,7 +32,6 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogLevel.ERROR
-import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.log.core.LogLevel.WARNING
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
@@ -63,7 +60,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
"ActiveUnlock",
DEBUG,
{ str1 = reason },
- { "initiate active unlock triggerReason=$str1" }
+ { "initiate active unlock triggerReason=$str1" },
)
}
@@ -75,7 +72,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"Skip requesting active unlock from wake reason that doesn't trigger face auth" +
" reason=${PowerManager.wakeReasonToString(int1)}"
- }
+ },
)
}
@@ -92,7 +89,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
DEBUG,
{ bool1 = deviceProvisioned },
- { "DEVICE_PROVISIONED state = $bool1" }
+ { "DEVICE_PROVISIONED state = $bool1" },
)
}
@@ -108,7 +105,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
str1 = originalErrMsg
int1 = msgId
},
- { "Face error received: $str1 msgId= $int1" }
+ { "Face error received: $str1 msgId= $int1" },
)
}
@@ -117,7 +114,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
DEBUG,
{ int1 = authUserId },
- { "Face authenticated for wrong user: $int1" }
+ { "Face authenticated for wrong user: $int1" },
)
}
@@ -130,7 +127,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
FP_LOG_TAG,
DEBUG,
{ int1 = authUserId },
- { "Fingerprint authenticated for wrong user: $int1" }
+ { "Fingerprint authenticated for wrong user: $int1" },
)
}
@@ -139,7 +136,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
FP_LOG_TAG,
DEBUG,
{ int1 = userId },
- { "Fingerprint disabled by DPM for userId: $int1" }
+ { "Fingerprint disabled by DPM for userId: $int1" },
)
}
@@ -148,7 +145,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
FP_LOG_TAG,
DEBUG,
{ int1 = mode },
- { "handleFingerprintLockoutReset: $int1" }
+ { "handleFingerprintLockoutReset: $int1" },
)
}
@@ -157,7 +154,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
FP_LOG_TAG,
DEBUG,
{ int1 = fingerprintRunningState },
- { "fingerprintRunningState: $int1" }
+ { "fingerprintRunningState: $int1" },
)
}
@@ -169,7 +166,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
bool1 = isStrongBiometric
},
- { "Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1" }
+ { "Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1" },
)
}
@@ -181,7 +178,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
bool1 = isStrongBiometric
},
- { "Face detected: userId: $int1, isStrongBiometric: $bool1" }
+ { "Face detected: userId: $int1, isStrongBiometric: $bool1" },
)
}
@@ -193,7 +190,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
bool1 = isStrongBiometric
},
- { "Fingerprint detected: userId: $int1, isStrongBiometric: $bool1" }
+ { "Fingerprint detected: userId: $int1, isStrongBiometric: $bool1" },
)
}
@@ -205,22 +202,13 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
str1 = originalErrMsg
int1 = msgId
},
- { "Fingerprint error received: $str1 msgId= $int1" }
- )
- }
-
- fun logInvalidSubId(subId: Int) {
- logBuffer.log(
- TAG,
- INFO,
- { int1 = subId },
- { "Previously active sub id $int1 is now invalid, will remove" }
+ { "Fingerprint error received: $str1 msgId= $int1" },
)
}
fun logPrimaryKeyguardBouncerChanged(
primaryBouncerIsOrWillBeShowing: Boolean,
- primaryBouncerFullyShown: Boolean
+ primaryBouncerFullyShown: Boolean,
) {
logBuffer.log(
TAG,
@@ -232,7 +220,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"handlePrimaryBouncerChanged " +
"primaryBouncerIsOrWillBeShowing=$bool1 primaryBouncerFullyShown=$bool2"
- }
+ },
)
}
@@ -249,7 +237,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
bool2 = occluded
bool3 = visible
},
- { "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)" }
+ { "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)" },
)
}
@@ -258,7 +246,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
ERROR,
{ int1 = userId },
- { "No Profile Owner or Device Owner supervision app found for User $int1" }
+ { "No Profile Owner or Device Owner supervision app found for User $int1" },
)
}
@@ -279,7 +267,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int2 = delay
str1 = "$errString"
},
- { "Fingerprint scheduling retry auth after $int2 ms due to($int1) -> $str1" }
+ { "Fingerprint scheduling retry auth after $int2 ms due to($int1) -> $str1" },
)
}
@@ -288,7 +276,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
WARNING,
{ int1 = retryCount },
- { "Retrying fingerprint attempt: $int1" }
+ { "Retrying fingerprint attempt: $int1" },
)
}
@@ -306,32 +294,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"sendPrimaryBouncerChanged primaryBouncerIsOrWillBeShowing=$bool1 " +
"primaryBouncerFullyShown=$bool2"
- }
- )
- }
-
- fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- int1 = subId
- str1 = "$serviceState"
- },
- { "handleServiceStateChange(subId=$int1, serviceState=$str1)" }
- )
- }
-
- fun logServiceStateIntent(action: String?, serviceState: ServiceState?, subId: Int) {
- logBuffer.log(
- TAG,
- VERBOSE,
- {
- str1 = action
- str2 = "$serviceState"
- int1 = subId
},
- { "action $str1 serviceState=$str2 subId=$int1" }
)
}
@@ -344,51 +307,16 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
},
- { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" }
+ { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" },
)
}
- fun logSimState(subId: Int, slotId: Int, state: Int) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- int1 = subId
- int2 = slotId
- long1 = state.toLong()
- },
- { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" }
- )
- }
-
- fun logSimStateFromIntent(action: String?, extraSimState: String?, slotId: Int, subId: Int) {
- logBuffer.log(
- TAG,
- VERBOSE,
- {
- str1 = action
- str2 = extraSimState
- int1 = slotId
- int2 = subId
- },
- { "action $str1 state: $str2 slotId: $int1 subid: $int2" }
- )
- }
-
- fun logSimUnlocked(subId: Int) {
- logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
- }
-
- fun logSubInfo(subInfo: SubscriptionInfo?) {
- logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" })
- }
-
fun logTimeFormatChanged(newTimeFormat: String?) {
logBuffer.log(
TAG,
DEBUG,
{ str1 = newTimeFormat },
- { "handleTimeFormatUpdate timeFormat=$str1" }
+ { "handleTimeFormatUpdate timeFormat=$str1" },
)
}
@@ -402,7 +330,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logUnexpectedFpCancellationSignalState(
fingerprintRunningState: Int,
- unlockPossible: Boolean
+ unlockPossible: Boolean,
) {
logBuffer.log(
TAG,
@@ -414,7 +342,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"Cancellation signal is not null, high chance of bug in " +
"fp auth lifecycle management. FP state: $int1, unlockPossible: $bool1"
- }
+ },
)
}
@@ -425,7 +353,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logUserRequestedUnlock(
requestOrigin: ActiveUnlockConfig.ActiveUnlockRequestOrigin,
reason: String?,
- dismissKeyguard: Boolean
+ dismissKeyguard: Boolean,
) {
logBuffer.log(
"ActiveUnlock",
@@ -435,7 +363,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
str2 = reason
bool1 = dismissKeyguard
},
- { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" }
+ { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" },
)
}
@@ -443,7 +371,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
flags: Int,
newlyUnlocked: Boolean,
userId: Int,
- message: String?
+ message: String?,
) {
logBuffer.log(
TAG,
@@ -457,7 +385,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"trustGrantedWithFlags[user=$int2] newlyUnlocked=$bool1 " +
"flags=${TrustGrantFlags(int1)} message=$str1"
- }
+ },
)
}
@@ -470,7 +398,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
bool2 = isNowTrusted
int1 = userId
},
- { "onTrustChanged[user=$int1] wasTrusted=$bool1 isNowTrusted=$bool2" }
+ { "onTrustChanged[user=$int1] wasTrusted=$bool1 isNowTrusted=$bool2" },
)
}
@@ -478,7 +406,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
secure: Boolean,
canDismissLockScreen: Boolean,
trusted: Boolean,
- trustManaged: Boolean
+ trustManaged: Boolean,
) {
logBuffer.log(
"KeyguardState",
@@ -492,7 +420,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"#update secure=$bool1 canDismissKeyguard=$bool2" +
" trusted=$bool3 trustManaged=$bool4"
- }
+ },
)
}
@@ -501,7 +429,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
VERBOSE,
{ bool1 = assistantVisible },
- { "TaskStackChanged for ACTIVITY_TYPE_ASSISTANT, assistant visible: $bool1" }
+ { "TaskStackChanged for ACTIVITY_TYPE_ASSISTANT, assistant visible: $bool1" },
)
}
@@ -510,7 +438,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
VERBOSE,
{ bool1 = allow },
- { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" }
+ { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" },
)
}
@@ -519,7 +447,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
VERBOSE,
{ bool1 = assistantVisible },
- { "Updating mAssistantVisible to new value: $bool1" }
+ { "Updating mAssistantVisible to new value: $bool1" },
)
}
@@ -531,7 +459,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
bool1 = isStrongBiometric
int1 = userId
},
- { "reporting successful biometric unlock: isStrongBiometric: $bool1, userId: $int1" }
+ { "reporting successful biometric unlock: isStrongBiometric: $bool1, userId: $int1" },
)
}
@@ -543,7 +471,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{
"MSG_BIOMETRIC_AUTHENTICATION_CONTINUE already queued up, " +
"ignoring updating FP listening state to $int1"
- }
+ },
)
}
@@ -551,7 +479,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
userId: Int,
oldValue: Boolean,
newValue: Boolean,
- context: String
+ context: String,
) {
logBuffer.log(
TAG,
@@ -568,7 +496,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
"old: $bool1, " +
"new: $bool2 " +
"context: $str1"
- }
+ },
)
}
@@ -591,7 +519,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
"plugged=$str1, " +
"chargingStatus=$int2, " +
"maxChargingWattage= $long2}"
- }
+ },
)
}
@@ -604,7 +532,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
DEBUG,
{ str1 = "$biometricSourceType" },
- { "notifying about enrollments changed: $str1" }
+ { "notifying about enrollments changed: $str1" },
)
}
@@ -616,7 +544,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
str1 = context
},
- { "userCurrentlySwitching: $str1, userId: $int1" }
+ { "userCurrentlySwitching: $str1, userId: $int1" },
)
}
@@ -628,7 +556,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
str1 = context
},
- { "userSwitchComplete: $str1, userId: $int1" }
+ { "userSwitchComplete: $str1, userId: $int1" },
)
}
@@ -637,7 +565,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
FP_LOG_TAG,
DEBUG,
{ int1 = acquireInfo },
- { "fingerprint acquire message: $int1" }
+ { "fingerprint acquire message: $int1" },
)
}
@@ -646,7 +574,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
TAG,
DEBUG,
{ bool1 = keepUnlocked },
- { "keepUnlockedOnFold changed to: $bool1" }
+ { "keepUnlockedOnFold changed to: $bool1" },
)
}
@@ -662,7 +590,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
bool1 = isUnlocked
},
- { "userStopped userId: $int1 isUnlocked: $bool1" }
+ { "userStopped userId: $int1 isUnlocked: $bool1" },
)
}
@@ -678,7 +606,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
int1 = userId
bool1 = isUnlocked
},
- { "userUnlockedInitialState userId: $int1 isUnlocked: $bool1" }
+ { "userUnlockedInitialState userId: $int1 isUnlocked: $bool1" },
)
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
new file mode 100644
index 000000000000..a81698ba3387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.keyguard.logging
+
+import android.content.Intent
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.dagger.SimLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "SimLog"
+
+/** Helper class for logging for SIM events */
+class SimLogger @Inject constructor(@SimLog private val logBuffer: LogBuffer) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logInvalidSubId(subId: Int) {
+ logBuffer.log(
+ TAG,
+ INFO,
+ { int1 = subId },
+ { "Previously active sub id $int1 is now invalid, will remove" },
+ )
+ }
+
+ fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = subId
+ str1 = "$serviceState"
+ },
+ { "handleServiceStateChange(subId=$int1, serviceState=$str1)" },
+ )
+ }
+
+ fun logServiceStateIntent(action: String?, serviceState: ServiceState?, subId: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = action
+ str2 = "$serviceState"
+ int1 = subId
+ },
+ { "action $str1 serviceState=$str2 subId=$int1" },
+ )
+ }
+
+ fun logServiceProvidersUpdated(intent: Intent) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
+ str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
+ str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+ },
+ { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" },
+ )
+ }
+
+ fun logSimState(subId: Int, slotId: Int, state: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = subId
+ int2 = slotId
+ long1 = state.toLong()
+ },
+ { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" },
+ )
+ }
+
+ fun logSimStateFromIntent(action: String?, extraSimState: String?, slotId: Int, subId: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = action
+ str2 = extraSimState
+ int1 = slotId
+ int2 = subId
+ },
+ { "action $str1 state: $str2 slotId: $int1 subid: $int2" },
+ )
+ }
+
+ fun logSimUnlocked(subId: Int) {
+ logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
+ }
+
+ fun logSubInfo(subInfo: SubscriptionInfo?) {
+ logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 60edaae21bc0..158623fa80af 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -280,6 +280,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
if (mLocalBluetoothManager == null) {
return;
}
+
+ // Remove the default padding of the system ui dialog
+ View container = dialog.findViewById(android.R.id.custom);
+ if (container != null && container.getParent() != null) {
+ View containerParent = (View) container.getParent();
+ containerParent.setPadding(0, 0, 0, 0);
+ }
+
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 664f3f834f86..9367cb5c0198 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -108,6 +108,7 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView
private final ImageView mIconView;
private final ImageView mGearIcon;
private final View mGearView;
+ private final View mDividerView;
DeviceItemViewHolder(@NonNull View itemView, Context context) {
super(itemView);
@@ -118,6 +119,7 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView
mIconView = itemView.requireViewById(R.id.bluetooth_device_icon);
mGearIcon = itemView.requireViewById(R.id.gear_icon_image);
mGearView = itemView.requireViewById(R.id.gear_icon);
+ mDividerView = itemView.requireViewById(R.id.divider);
}
public void bindView(DeviceItem item, HearingDeviceItemCallback callback) {
@@ -153,6 +155,7 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView
mGearIcon.getDrawable().mutate().setTint(tintColor);
mGearView.setOnClickListener(view -> callback.onDeviceItemGearClicked(item, view));
+ mDividerView.setBackgroundColor(tintColor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b39aae94ed4e..a5bd559dcbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
@@ -54,6 +55,7 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.RotationUtils;
@@ -211,9 +213,13 @@ public class AuthController implements
}
};
- private void closeDialog(String reason) {
+ private void closeDialog(String reasonString) {
+ closeDialog(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, reasonString);
+ }
+
+ private void closeDialog(@DismissedReason int reason, String reasonString) {
if (isShowing()) {
- Log.i(TAG, "Close BP, reason :" + reason);
+ Log.i(TAG, "Close BP, reason :" + reasonString);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
@@ -223,8 +229,7 @@ public class AuthController implements
try {
if (mReceiver != null) {
- mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
+ mReceiver.onDialogDismissed(reason, null /* credentialAttestation */);
mReceiver = null;
}
} catch (RemoteException e) {
@@ -251,25 +256,7 @@ public class AuthController implements
private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
- if (mCurrentDialog != null) {
- try {
- mCurrentDialog.dismissWithoutCallback(true /* animate */);
- mCurrentDialog = null;
-
- for (Callback cb : mCallbacks) {
- cb.onBiometricPromptDismissed();
- }
-
- if (mReceiver != null) {
- mReceiver.onDialogDismissed(
- BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
- mReceiver = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception", e);
- }
- }
+ closeDialog("owner not in foreground");
}
/**
@@ -1271,8 +1258,42 @@ public class AuthController implements
if (!promptInfo.isAllowBackgroundAuthentication() && isOwnerInBackground()) {
cancelIfOwnerIsNotInForeground();
} else {
- mCurrentDialog.show(mWindowManager);
+ WindowManager wm = getWindowManagerForUser(userId);
+ if (wm != null) {
+ mCurrentDialog.show(wm);
+ } else {
+ closeDialog(BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM,
+ "unable to get WM instance for user");
+ }
+ }
+ }
+
+ @Nullable
+ private WindowManager getWindowManagerForUser(int userId) {
+ if (!mUserManager.isVisibleBackgroundUsersSupported()) {
+ return mWindowManager;
+ }
+ UserManager um = mContext.createContextAsUser(UserHandle.of(userId),
+ 0 /* flags */).getSystemService(UserManager.class);
+ if (um == null) {
+ Log.e(TAG, "unable to get UserManager for user=" + userId);
+ return null;
+ }
+ if (!um.isUserVisible()) {
+ // not visible user - use default window manager
+ return mWindowManager;
+ }
+ int displayId = um.getMainDisplayIdAssignedToUser();
+ if (displayId == INVALID_DISPLAY) {
+ Log.e(TAG, "unable to get display assigned to user=" + userId);
+ return null;
+ }
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ Log.e(TAG, "unable to get Display for user=" + userId);
+ return null;
}
+ return mContext.createDisplayContext(display).getSystemService(WindowManager.class);
}
private void onDialogDismissed(@DismissedReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index aabfbd11b70e..65c01ed9eecd 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -710,9 +710,16 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
@Override
public void onShareButtonTapped() {
if (clipboardSharedTransitions()) {
- if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
- finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
- IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ switch (mClipboardModel.getType()) {
+ case TEXT:
+ case URI:
+ finish(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+ IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ break;
+ case IMAGE:
+ finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+ IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 8f1854f93fe4..17f4f0c83d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
@@ -43,19 +43,16 @@ abstract class CommunalDatabase : RoomDatabase() {
* @param callback An optional callback registered to the database. Only effective when a
* new instance is created.
*/
- fun getInstance(
- context: Context,
- callback: Callback? = null,
- ): CommunalDatabase {
+ fun getInstance(context: Context, callback: Callback? = null): CommunalDatabase {
if (instance == null) {
instance =
Room.databaseBuilder(
context,
CommunalDatabase::class.java,
- context.resources.getString(R.string.config_communalDatabase)
+ context.resources.getString(R.string.config_communalDatabase),
)
.also { builder ->
- builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
+ builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
@@ -103,5 +100,21 @@ abstract class CommunalDatabase : RoomDatabase() {
)
}
}
+
+ /**
+ * This migration adds a span_y column to the communal_widget_table and sets its default
+ * value to 3.
+ */
+ @VisibleForTesting
+ val MIGRATION_3_4 =
+ object : Migration(3, 4) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 3 to 4")
+ db.execSQL(
+ "ALTER TABLE communal_widget_table " +
+ "ADD COLUMN span_y INTEGER NOT NULL DEFAULT 3"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index e33aead11842..f9d2a843c213 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -40,6 +40,12 @@ data class CommunalWidgetItem(
*/
@ColumnInfo(name = "user_serial_number", defaultValue = "$USER_SERIAL_NUMBER_UNDEFINED")
val userSerialNumber: Int,
+
+ /**
+ * The vertical span of the widget. Span_Y default value corresponds to
+ * CommunalContentSize.HALF.span
+ */
+ @ColumnInfo(name = "span_y", defaultValue = "3") val spanY: Int,
) {
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 93b86bd10133..5dd4c1cb7f72 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -25,6 +25,7 @@ import androidx.room.RoomDatabase
import androidx.room.Transaction
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
import com.android.systemui.dagger.SysUISingleton
@@ -153,14 +154,15 @@ interface CommunalWidgetDao {
@Query(
"INSERT INTO communal_widget_table" +
- "(widget_id, component_name, item_id, user_serial_number) " +
- "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber)"
+ "(widget_id, component_name, item_id, user_serial_number, span_y) " +
+ "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY)"
)
fun insertWidget(
widgetId: Int,
componentName: String,
itemId: Long,
userSerialNumber: Int,
+ spanY: Int = 3,
): Long
@Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
@@ -169,6 +171,9 @@ interface CommunalWidgetDao {
@Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid")
fun updateItemRank(itemUid: Long, order: Int)
+ @Query("UPDATE communal_widget_table SET span_y = :spanY WHERE widget_id = :widgetId")
+ fun updateWidgetSpanY(widgetId: Int, spanY: Int)
+
@Query("DELETE FROM communal_widget_table") fun clearCommunalWidgetsTable()
@Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
@@ -189,12 +194,14 @@ interface CommunalWidgetDao {
provider: ComponentName,
rank: Int? = null,
userSerialNumber: Int,
+ spanY: Int = CommunalContentSize.HALF.span,
): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
@@ -204,6 +211,7 @@ interface CommunalWidgetDao {
componentName: String,
rank: Int? = null,
userSerialNumber: Int,
+ spanY: Int = 3,
): Long {
val widgets = getWidgetsNow()
@@ -224,6 +232,7 @@ interface CommunalWidgetDao {
componentName = componentName,
itemId = insertItemRank(newRank),
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
@@ -246,7 +255,8 @@ interface CommunalWidgetDao {
clearCommunalItemRankTable()
state.widgets.forEach {
- addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ val spanY = if (it.spanY != 0) it.spanY else CommunalContentSize.HALF.span
+ addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, spanY)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 6cdd9fffe077..3312f3cac64b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -92,6 +92,14 @@ interface CommunalWidgetRepository {
/** Aborts the restore process and removes files from disk if necessary. */
fun abortRestoreWidgets()
+
+ /**
+ * Update the spanY of a widget in the database.
+ *
+ * @param widgetId id of the widget to update.
+ * @param spanY new spanY value for the widget.
+ */
+ fun updateWidgetSpanY(widgetId: Int, spanY: Int)
}
@SysUISingleton
@@ -118,20 +126,30 @@ constructor(
/** Widget metadata from database + matching [AppWidgetProviderInfo] if any. */
private val widgetEntries: Flow<List<CommunalWidgetEntry>> =
- combine(
- communalWidgetDao.getWidgets(),
- communalWidgetHost.appWidgetProviders,
- ) { entries, providers ->
+ combine(communalWidgetDao.getWidgets(), communalWidgetHost.appWidgetProviders) {
+ entries,
+ providers ->
entries.mapNotNull { (rank, widget) ->
CommunalWidgetEntry(
appWidgetId = widget.widgetId,
componentName = widget.componentName,
rank = rank.rank,
- providerInfo = providers[widget.widgetId]
+ providerInfo = providers[widget.widgetId],
)
}
}
+ override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
+ bgScope.launch {
+ communalWidgetDao.updateWidgetSpanY(widgetId, spanY)
+ logger.i({ "Updated spanY of widget $int1 to $int2." }) {
+ int1 = widgetId
+ int2 = spanY
+ }
+ backupManager.dataChanged()
+ }
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
widgetEntries
@@ -197,6 +215,7 @@ constructor(
provider = provider,
rank = rank,
userSerialNumber = userManager.getUserSerialNumber(user.identifier),
+ spanY = 3,
)
backupManager.dataChanged()
} else {
@@ -325,6 +344,7 @@ constructor(
componentName = restoredWidget.componentName
rank = restoredWidget.rank
userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
+ spanY = restoredWidget.spanY
}
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
@@ -383,6 +403,7 @@ constructor(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
rank = entry.rank,
+ spanY = entry.spanY,
)
}
@@ -400,6 +421,7 @@ constructor(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
rank = entry.rank,
+ spanY = entry.spanY,
)
}
@@ -412,6 +434,7 @@ constructor(
componentName = componentName,
icon = session.icon,
user = session.user,
+ spanY = entry.spanY,
)
} else {
null
@@ -423,5 +446,6 @@ constructor(
val componentName: String,
val rank: Int,
var providerInfo: AppWidgetProviderInfo? = null,
+ var spanY: Int = 3,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index bc14ae1eaff4..7602a7afce4e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -38,5 +38,8 @@ message CommunalHubState {
// Serial number of the user associated with the widget.
int32 user_serial_number = 4;
+
+ // The vertical span of the widget
+ int32 span_y = 5;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 63b1a14b3135..bcbc8f65ce36 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -31,6 +31,7 @@ sealed interface CommunalWidgetContentModel {
override val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
override val rank: Int,
+ val spanY: Int = 3,
) : CommunalWidgetContentModel
/** Widget is pending installation */
@@ -40,5 +41,6 @@ sealed interface CommunalWidgetContentModel {
val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
+ val spanY: Int = 3,
) : CommunalWidgetContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
new file mode 100644
index 000000000000..7aad33da97b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.runtime.snapshotFlow
+import com.android.app.tracing.coroutines.coroutineScope
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+
+enum class DragHandle {
+ TOP,
+ BOTTOM,
+}
+
+data class ResizeInfo(
+ /**
+ * The number of spans to resize by. A positive number indicates expansion, whereas a negative
+ * number indicates shrinking.
+ */
+ val spans: Int,
+ /** The drag handle which was used to resize the element. */
+ val fromHandle: DragHandle,
+)
+
+class ResizeableItemFrameViewModel : ExclusiveActivatable() {
+ private data class GridLayoutInfo(
+ val minSpan: Int,
+ val maxSpan: Int,
+ val heightPerSpanPx: Float,
+ val verticalItemSpacingPx: Float,
+ val currentRow: Int,
+ val currentSpan: Int,
+ )
+
+ /**
+ * The layout information necessary in order to calculate the pixel offsets of the drag anchor
+ * points.
+ */
+ private val gridLayoutInfo = MutableStateFlow<GridLayoutInfo?>(null)
+
+ val topDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+ val bottomDragState = AnchoredDraggableState(0, DraggableAnchors { 0 at 0f })
+
+ /** Emits a [ResizeInfo] when the element is resized using a drag gesture. */
+ val resizeInfo: Flow<ResizeInfo> =
+ merge(
+ snapshotFlow { topDragState.settledValue }.map { ResizeInfo(-it, DragHandle.TOP) },
+ snapshotFlow { bottomDragState.settledValue }
+ .map { ResizeInfo(it, DragHandle.BOTTOM) },
+ )
+ .dropWhile { it.spans == 0 }
+ .distinctUntilChanged()
+
+ /**
+ * Sets the necessary grid layout information needed for calculating the pixel offsets of the
+ * drag anchors.
+ */
+ fun setGridLayoutInfo(
+ verticalItemSpacingPx: Float,
+ verticalContentPaddingPx: Float,
+ viewportHeightPx: Int,
+ maxItemSpan: Int,
+ minItemSpan: Int,
+ currentRow: Int,
+ currentSpan: Int,
+ ) {
+ require(maxItemSpan >= minItemSpan) {
+ "Maximum item span of $maxItemSpan cannot be less than the minimum span of $minItemSpan"
+ }
+ require(minItemSpan in 1..maxItemSpan) {
+ "Minimum span must be between 1 and $maxItemSpan, but was $minItemSpan"
+ }
+ require(currentSpan % minItemSpan == 0) {
+ "Current span of $currentSpan is not a multiple of the minimum span of $minItemSpan"
+ }
+ val availableHeight = viewportHeightPx - verticalContentPaddingPx
+ val totalSpacing = verticalItemSpacingPx * ((maxItemSpan / minItemSpan) - 1)
+ val heightPerSpanPx = (availableHeight - totalSpacing) / maxItemSpan
+ gridLayoutInfo.value =
+ GridLayoutInfo(
+ minSpan = minItemSpan,
+ maxSpan = maxItemSpan,
+ heightPerSpanPx = heightPerSpanPx,
+ verticalItemSpacingPx = verticalItemSpacingPx,
+ currentRow = currentRow,
+ currentSpan = currentSpan,
+ )
+ }
+
+ private fun calculateAnchorsForHandle(
+ handle: DragHandle,
+ layoutInfo: GridLayoutInfo,
+ ): DraggableAnchors<Int> {
+
+ if (!isDragAllowed(handle, layoutInfo)) {
+ return DraggableAnchors { 0 at 0f }
+ }
+
+ val (
+ minItemSpan,
+ maxItemSpan,
+ heightPerSpanPx,
+ verticalSpacingPx,
+ currentRow,
+ currentSpan,
+ ) = layoutInfo
+
+ // The maximum row this handle can be dragged to.
+ val maxRow =
+ if (handle == DragHandle.TOP) {
+ (currentRow + currentSpan - minItemSpan).coerceAtLeast(0)
+ } else {
+ maxItemSpan
+ }
+
+ // The minimum row this handle can be dragged to.
+ val minRow =
+ if (handle == DragHandle.TOP) {
+ 0
+ } else {
+ (currentRow + minItemSpan).coerceAtMost(maxItemSpan)
+ }
+
+ // The current row position of this handle
+ val currentPosition = if (handle == DragHandle.TOP) currentRow else currentRow + currentSpan
+
+ return DraggableAnchors {
+ for (targetRow in minRow..maxRow step minItemSpan) {
+ val diff = targetRow - currentPosition
+ val spacing = diff / minItemSpan * verticalSpacingPx
+ diff at diff * heightPerSpanPx + spacing
+ }
+ }
+ }
+
+ private fun isDragAllowed(handle: DragHandle, layoutInfo: GridLayoutInfo): Boolean {
+ val minItemSpan = layoutInfo.minSpan
+ val maxItemSpan = layoutInfo.maxSpan
+ val currentRow = layoutInfo.currentRow
+ val currentSpan = layoutInfo.currentSpan
+ val atMinSize = currentSpan == minItemSpan
+
+ // If already at the minimum size and in the first row, item cannot be expanded from the top
+ if (handle == DragHandle.TOP && currentRow == 0 && atMinSize) {
+ return false
+ }
+
+ // If already at the minimum size and occupying the last row, item cannot be expanded from
+ // the
+ // bottom
+ if (handle == DragHandle.BOTTOM && (currentRow + currentSpan) == maxItemSpan && atMinSize) {
+ return false
+ }
+
+ // If at maximum size, item can only be shrunk from the bottom and not the top.
+ if (handle == DragHandle.TOP && currentSpan == maxItemSpan) {
+ return false
+ }
+
+ return true
+ }
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope("ResizeableItemFrameViewModel.onActivated") {
+ gridLayoutInfo
+ .filterNotNull()
+ .onEach { layoutInfo ->
+ topDragState.updateAnchors(
+ calculateAnchorsForHandle(DragHandle.TOP, layoutInfo)
+ )
+ bottomDragState.updateAnchors(
+ calculateAnchorsForHandle(DragHandle.BOTTOM, layoutInfo)
+ )
+ }
+ .launchIn(this)
+ awaitCancellation()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
index 099e3fcf227d..4b9ac1d58b57 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -21,9 +21,10 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.TextClock
import com.android.internal.util.Preconditions
-import com.android.systemui.res.R
+import com.android.systemui.Flags
import com.android.systemui.complication.DreamClockTimeComplication
import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@@ -71,9 +72,13 @@ interface DreamClockTimeComplicationComponent {
/* root = */ null,
/* attachToRoot = */ false,
) as TextClock,
- "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+ "R.layout.dream_overlay_complication_clock_time did not properly inflate",
)
- view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ if (Flags.dreamOverlayUpdatedFont()) {
+ view.setFontVariationSettings("'wght' 600, 'opsz' 96")
+ } else {
+ view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ }
return view
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a94fbd937afc..a5b22775f3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -56,6 +56,7 @@ import com.android.systemui.rotationlock.RotationLockNewModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.settings.MultiUserUtilsModule;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.startable.Dependencies;
@@ -178,9 +179,9 @@ public abstract class ReferenceSystemUIModule {
@Provides
@SysUISingleton
static IndividualSensorPrivacyController provideIndividualSensorPrivacyController(
- SensorPrivacyManager sensorPrivacyManager) {
+ SensorPrivacyManager sensorPrivacyManager, UserTracker userTracker) {
IndividualSensorPrivacyController spC = new IndividualSensorPrivacyControllerImpl(
- sensorPrivacyManager);
+ sensorPrivacyManager, userTracker);
spC.init();
return spC;
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 373279cec5d1..462e820f68da 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -20,6 +20,8 @@ import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import dagger.Binds
@@ -39,4 +41,9 @@ interface DisplayModule {
fun bindsDeviceStateRepository(
deviceStateRepository: DeviceStateRepositoryImpl
): DeviceStateRepository
+
+ @Binds
+ fun bindsFocusedDisplayRepository(
+ focusedDisplayRepository: FocusedDisplayRepositoryImpl
+ ): FocusedDisplayRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
index dc07cca08d7b..6fc08f6bfab0 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FocusedDisplayRepository.kt
@@ -19,8 +19,7 @@ package com.android.systemui.display.data.repository
import android.annotation.MainThread
import android.view.Display.DEFAULT_DISPLAY
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.FocusedDisplayRepoLog
@@ -38,20 +37,30 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Repository tracking display focus. */
+interface FocusedDisplayRepository {
+ /** Provides the currently focused display. */
+ val focusedDisplayId: StateFlow<Int>
+}
+
@SysUISingleton
@MainThread
-class FocusedDisplayRepository
+class FocusedDisplayRepositoryImpl
@Inject
constructor(
- @Application val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
+ @Background val backgroundScope: CoroutineScope,
+ @Background private val backgroundExecutor: Executor,
transitions: ShellTransitions,
@FocusedDisplayRepoLog logBuffer: LogBuffer,
-) {
+) : FocusedDisplayRepository {
val focusedTask: Flow<Int> =
- conflatedCallbackFlow {
- val listener = FocusTransitionListener { displayId -> trySend(displayId) }
- transitions.setFocusTransitionListener(listener, mainExecutor)
+ conflatedCallbackFlow<Int> {
+ val listener =
+ object : FocusTransitionListener {
+ override fun onFocusedDisplayChanged(displayId: Int) {
+ trySend(displayId)
+ }
+ }
+ transitions.setFocusTransitionListener(listener, backgroundExecutor)
awaitClose { transitions.unsetFocusTransitionListener(listener) }
}
.onEach {
@@ -63,7 +72,6 @@ constructor(
)
}
- /** Provides the currently focused display. */
- val focusedDisplayId: StateFlow<Int>
- get() = focusedTask.stateIn(scope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
+ override val focusedDisplayId: StateFlow<Int>
+ get() = focusedTask.stateIn(backgroundScope, SharingStarted.Eagerly, DEFAULT_DISPLAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
new file mode 100644
index 000000000000..8b6cc8cb4540
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.dreams.ui.viewmodel
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Handles user input for the dream scene. */
+class DreamUserActionsViewModel @AssistedInject constructor() : UserActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ setActions(emptyMap())
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): DreamUserActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 32e7f41f36b8..55639697674f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -48,7 +48,7 @@ constructor(
) {
companion object {
- const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3500
+ const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 5000
}
private val timeoutMillis: Long
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
index 5a008bddc748..7711c48924cc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
@@ -38,7 +38,7 @@ class InputDeviceRepository
constructor(
@Background private val backgroundHandler: Handler,
@Background private val backgroundScope: CoroutineScope,
- private val inputManager: InputManager
+ private val inputManager: InputManager,
) {
sealed interface DeviceChange
@@ -50,11 +50,11 @@ constructor(
data object FreshStart : DeviceChange
/**
- * Emits collection of all currently connected keyboards and what was the last [DeviceChange].
- * It emits collection so that every new subscriber to this SharedFlow can get latest state of
- * all keyboards. Otherwise we might get into situation where subscriber timing on
- * initialization matter and later subscriber will only get latest device and will miss all
- * previous devices.
+ * Emits collection of all currently connected input devices and what was the last
+ * [DeviceChange]. It emits collection so that every new subscriber to this SharedFlow can get
+ * latest state of all input devices. Otherwise we might get into situation where subscriber
+ * timing on initialization matter and later subscriber will only get latest device and will
+ * miss all previous devices.
*/
// TODO(b/351984587): Replace with StateFlow
@SuppressLint("SharedFlowCreation")
@@ -79,11 +79,7 @@ constructor(
inputManager.registerInputDeviceListener(listener, backgroundHandler)
awaitClose { inputManager.unregisterInputDeviceListener(listener) }
}
- .shareIn(
- scope = backgroundScope,
- started = SharingStarted.Lazily,
- replay = 1,
- )
+ .shareIn(scope = backgroundScope, started = SharingStarted.Lazily, replay = 1)
private fun <T> SendChannel<T>.sendWithLogging(element: T) {
trySendWithFailureLogging(element, TAG)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
index f49cfdda8b0a..021c069008c3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/CommandLineKeyboardRepository.kt
@@ -50,6 +50,8 @@ class CommandLineKeyboardRepository @Inject constructor(commandRegistry: Command
private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
+ override val connectedKeyboards: Flow<Set<Keyboard>> = MutableStateFlow(emptySet())
+
init {
Log.i(TAG, "initializing shell command $COMMAND")
commandRegistry.registerCommand(COMMAND) { KeyboardCommand() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index a20dfa5a4c3e..3329fe29dade 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -61,6 +61,9 @@ interface KeyboardRepository {
*/
val newlyConnectedKeyboard: Flow<Keyboard>
+ /** Emits set of currently connected keyboards */
+ val connectedKeyboards: Flow<Set<Keyboard>>
+
/**
* Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only
* happen when physical keyboard is connected
@@ -74,7 +77,7 @@ class KeyboardRepositoryImpl
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val inputManager: InputManager,
- inputDeviceRepository: InputDeviceRepository
+ inputDeviceRepository: InputDeviceRepository,
) : KeyboardRepository {
@FlowPreview
@@ -93,6 +96,13 @@ constructor(
.mapNotNull { deviceIdToKeyboard(it) }
.flowOn(backgroundDispatcher)
+ override val connectedKeyboards: Flow<Set<Keyboard>> =
+ inputDeviceRepository.deviceChange
+ .map { (deviceIds, _) -> deviceIds }
+ .map { deviceIds -> deviceIds.filter { isPhysicalFullKeyboard(it) } }
+ .distinctUntilChanged()
+ .map { deviceIds -> deviceIds.mapNotNull { deviceIdToKeyboard(it) }.toSet() }
+
override val isAnyKeyboardConnected: Flow<Boolean> =
inputDeviceRepository.deviceChange
.map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 9443570705c8..1497026b5750 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -19,10 +19,12 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
@@ -50,11 +52,29 @@ constructor(
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
+ val animationDuration =
+ if (it.to == KeyguardState.AOD && isLastSleepDueToFold) {
+ // Do not animate the scrim when folding as we want to cover the screen
+ // with the scrim immediately while displays are switching.
+ // This is needed to play the fold to AOD animation which starts with
+ // fully black screen (see FoldAodAnimationController)
+ 0L
+ } else {
+ DEFAULT_REVEAL_DURATION
+ }
+
+ lightRevealScrimRepository.startRevealAmountAnimator(
+ willBeRevealedInState(it.to),
+ duration = animationDuration
+ )
}
}
}
+ private val isLastSleepDueToFold: Boolean
+ get() = powerInteractor.get().detailedWakefulness.value
+ .lastSleepReason == WakeSleepReason.FOLD
+
/**
* Whenever a keyguard transition starts, sample the latest reveal effect from the repository
* and use that for the starting transition.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 8386628f4c83..57cb10ff9367 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -121,7 +121,10 @@ object KeyguardPreviewClockViewBinder {
private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
constraints.apply {
constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
+ // The following two lines make lockscreen_clock_view_large is constrained to available
+ // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
+ constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+ constrainMaxHeight(R.id.lockscreen_clock_view_large, 0)
val largeClockTopMargin =
SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
@@ -138,7 +141,7 @@ object KeyguardPreviewClockViewBinder {
R.id.lockscreen_clock_view_large,
ConstraintSet.END,
PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.END,
)
// In preview, we'll show UDFPS icon for UDFPS devices
@@ -160,14 +163,14 @@ object KeyguardPreviewClockViewBinder {
BOTTOM,
PARENT_ID,
BOTTOM,
- clockBottomMargin
+ clockBottomMargin,
)
}
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height),
)
connect(
R.id.lockscreen_clock_view,
@@ -175,7 +178,7 @@ object KeyguardPreviewClockViewBinder {
PARENT_ID,
START,
context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
)
val smallClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
@@ -188,7 +191,7 @@ object KeyguardPreviewClockViewBinder {
context: Context,
rootView: ConstraintLayout,
previewClock: ClockController,
- viewModel: KeyguardPreviewClockViewModel
+ viewModel: KeyguardPreviewClockViewModel,
) {
val cs = ConstraintSet().apply { clone(rootView) }
applyClockDefaultConstraints(context, cs)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c0b9efaaec01..914730e1ea4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -25,7 +25,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,14 +49,12 @@ constructor(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER))
private val alphaForAnimationStep: (Float) -> Float =
when {
SceneContainerFlag.isEnabled -> { step ->
- 1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+ 1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
}
else -> { step -> 1f - step }
}
@@ -64,7 +62,7 @@ constructor(
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- onStep = alphaForAnimationStep
+ onStep = alphaForAnimationStep,
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
@@ -76,8 +74,8 @@ constructor(
duration = 250.milliseconds,
onStep = { 1f - it },
onCancel = { 0f },
- onFinish = { 0f }
+ onFinish = { 0f },
),
- flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f)
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 7ebdd88bfe6f..4e975ff27361 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -480,6 +480,16 @@ public class LogModule {
}
/**
+ * Provides a {@link LogBuffer} for use by SIM events.
+ */
+ @Provides
+ @SysUISingleton
+ @SimLog
+ public static LogBuffer provideSimLogBuffer(LogBufferFactory factory) {
+ return factory.create("SimLog", 500);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SimLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/SimLog.kt
new file mode 100644
index 000000000000..64fc6e70f47f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SimLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for SIM events. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SimLog
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f7a505a413d1..5048a5dfdec0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -326,6 +326,11 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
+ if (!mInRejectedExclusion) {
+ // Log successful back gesture to contextual edu stats
+ mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe,
+ GestureType.BACK);
+ }
}
@Override
@@ -1153,8 +1158,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
if (mAllowGesture) {
if (mBackAnimation != null) {
mBackAnimation.onThresholdCrossed();
- mOverviewProxyService.updateContextualEduStats(
- mIsTrackpadThreeFingerSwipe, GestureType.BACK);
} else {
pilferPointers();
}
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 66ac01ab95a0..65c29b829429 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -52,9 +52,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.dimensionResource
@@ -62,12 +61,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -191,58 +192,22 @@ constructor(
val context = inflater.context
val composeView =
ComposeView(context).apply {
- setBackPressedDispatcher()
- setContent {
- PlatformTheme {
- val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
-
- AnimatedVisibility(
- visible = visible,
- modifier =
- Modifier.windowInsetsPadding(WindowInsets.navigationBars)
- .thenIf(notificationScrimClippingParams.isEnabled) {
- Modifier.notificationScrimClip(
- notificationScrimClippingParams.leftInset,
- notificationScrimClippingParams.top,
- notificationScrimClippingParams.rightInset,
- notificationScrimClippingParams.bottom,
- notificationScrimClippingParams.radius,
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ it.viewRootImpl.onBackInvokedDispatcher
)
}
- .graphicsLayer { elevation = 4.dp.toPx() },
- ) {
- val isEditing by
- viewModel.containerViewModel.editModeViewModel.isEditing
- .collectAsStateWithLifecycle()
- val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
- AnimatedContent(
- targetState = isEditing,
- transitionSpec = {
- fadeIn(animationSpecEditMode) togetherWith
- fadeOut(animationSpecEditMode)
- },
- label = "EditModeAnimatedContent",
- ) { editing ->
- if (editing) {
- val qqsPadding by
- viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
- EditMode(
- viewModel = viewModel.containerViewModel.editModeViewModel,
- modifier =
- Modifier.fillMaxWidth()
- .padding(top = { qqsPadding })
- .padding(
- horizontal = {
- QuickSettingsShade.Dimensions.Padding
- .roundToPx()
- }
- ),
- )
- } else {
- CollapsableQuickSettingsSTL()
- }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
}
- }
+ )
+ setContent { this@QSFragmentCompose.Content() }
}
}
}
@@ -261,6 +226,58 @@ constructor(
return frame
}
+ @Composable
+ private fun Content() {
+ PlatformTheme {
+ val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
+
+ AnimatedVisibility(
+ visible = visible,
+ modifier =
+ Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
+ notificationScrimClippingParams.isEnabled
+ ) {
+ Modifier.notificationScrimClip(
+ notificationScrimClippingParams.leftInset,
+ notificationScrimClippingParams.top,
+ notificationScrimClippingParams.rightInset,
+ notificationScrimClippingParams.bottom,
+ notificationScrimClippingParams.radius,
+ )
+ },
+ ) {
+ val isEditing by
+ viewModel.containerViewModel.editModeViewModel.isEditing
+ .collectAsStateWithLifecycle()
+ val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
+ AnimatedContent(
+ targetState = isEditing,
+ transitionSpec = {
+ fadeIn(animationSpecEditMode) togetherWith fadeOut(animationSpecEditMode)
+ },
+ label = "EditModeAnimatedContent",
+ ) { editing ->
+ if (editing) {
+ val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+ EditMode(
+ viewModel = viewModel.containerViewModel.editModeViewModel,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(top = { qqsPadding })
+ .padding(
+ horizontal = {
+ QuickSettingsShade.Dimensions.Padding.roundToPx()
+ }
+ ),
+ )
+ } else {
+ CollapsableQuickSettingsSTL()
+ }
+ }
+ }
+ }
+ }
+
/**
* STL that contains both QQS (tiles) and QS (brightness, tiles, footer actions), but no Edit
* mode. It tracks [QSFragmentComposeViewModel.expansionState] to drive the transition between
@@ -274,7 +291,7 @@ constructor(
transitions =
transitions {
from(QuickQuickSettings, QuickSettings) {
- quickQuickSettingsToQuickSettings()
+ quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get)
}
},
)
@@ -519,6 +536,10 @@ constructor(
onDispose { qqsVisible.value = false }
}
+ val squishiness by
+ viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel
+ .squishiness
+ .collectAsStateWithLifecycle()
Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
Box(
modifier =
@@ -532,7 +553,16 @@ constructor(
topFromRoot + coordinates.size.height,
)
}
- .onSizeChanged { size -> qqsHeight.value = size.height }
+ // Use an approach layout to determien the height without squishiness, as
+ // that's the value that NPVC and QuickSettingsController care about
+ // (measured height).
+ .approachLayout(isMeasurementApproachInProgress = { squishiness < 1f }) {
+ measurable,
+ constraints ->
+ qqsHeight.value = lookaheadSize.height
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+ }
.padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() })
) {
val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
@@ -649,23 +679,6 @@ constructor(
}
}
-private fun View.setBackPressedDispatcher() {
- repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- setViewTreeOnBackPressedDispatcherOwner(
- object : OnBackPressedDispatcherOwner {
- override val onBackPressedDispatcher =
- OnBackPressedDispatcher().apply {
- setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher)
- }
-
- override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
- }
- )
- }
- }
-}
-
private suspend inline fun <Listener : Any, Data> setListenerJob(
listenerFlow: MutableStateFlow<Listener?>,
dataFlow: Flow<Data>,
@@ -707,6 +720,14 @@ object SceneKeys {
else -> QuickSettings
}
}
+
+ val QqsTileElementMatcher =
+ object : ElementMatcher {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return content == SceneKeys.QuickQuickSettings &&
+ ElementKeys.TileElementMatcher.matches(key, content)
+ }
+ }
}
suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
index 1514986d16d9..9e3945ecba57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
@@ -17,13 +17,23 @@
package com.android.systemui.qs.composefragment.ui
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.qs.composefragment.SceneKeys
import com.android.systemui.qs.shared.ui.ElementKeys
-fun TransitionBuilder.quickQuickSettingsToQuickSettings() {
+fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) {
fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) }
fractionRange(start = 0.9f) { fade(ElementKeys.FooterActions) }
anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor)
+
+ sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage())
+
+ // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back
+ // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief
+ // overlap, but because they are really faint, it looks better than complete black without
+ // overlap.
+ fractionRange(end = 0.6f) { fade(SceneKeys.QqsTileElementMatcher) }
+ anchoredTranslate(SceneKeys.QqsTileElementMatcher, ElementKeys.GridAnchor)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 2d4e358414d5..7a8b2c2eb685 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
@@ -71,6 +72,7 @@ constructor(
private val configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val squishinessInteractor: TileSquishinessInteractor,
+ private val paginatedGridViewModel: PaginatedGridViewModel,
@Assisted private val lifecycleScope: LifecycleCoroutineScope,
) : Dumpable, ExclusiveActivatable() {
val footerActionsViewModel =
@@ -292,6 +294,9 @@ constructor(
*/
var collapseExpandAccessibilityAction: Runnable? = null
+ val inFirstPage: Boolean
+ get() = paginatedGridViewModel.inFirstPage
+
override suspend fun onActivated(): Nothing {
hydrateSquishinessInteractor()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 083f529a21da..e749475479d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -31,8 +31,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -76,6 +78,11 @@ constructor(
val pagerState = rememberPagerState(0) { pages.size }
+ // Used to track if this is currently in the first page or not, for animations
+ LaunchedEffect(key1 = pagerState) {
+ snapshotFlow { pagerState.currentPage == 0 }.collect { viewModel.inFirstPage = it }
+ }
+
Column {
HorizontalPager(
state = pagerState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
index ada1ef4de15d..91f76418824e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.approachLayout
import kotlin.math.roundToInt
/**
@@ -27,17 +27,22 @@ import kotlin.math.roundToInt
* [squishiness] on the measure/layout pass.
*
* The squished composable will be center aligned.
+ *
+ * Use an [approachLayout] to indicate that this should be measured in the lookahead step without
+ * using squishiness. If a parent of this node needs to determine unsquished height, they should
+ * also use an approachLayout tracking the squishiness.
*/
fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
- return layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- val actualHeight = placeable.height
- val squishedHeight = actualHeight * squishiness()
+ return approachLayout(isMeasurementApproachInProgress = { squishiness() < 1 }) { measurable, _
+ ->
+ val squishinessValue = squishiness()
+ val expectedHeight = lookaheadSize.height
+
+ val placeable = measurable.measure(lookaheadConstraints)
+ val squishedHeight = (expectedHeight * squishinessValue).roundToInt()
// Center the content by moving it UP (squishedHeight < actualHeight)
- val scroll = (squishedHeight - actualHeight) / 2
+ val scroll = (squishedHeight - expectedHeight) / 2
- layout(placeable.width, squishedHeight.roundToInt()) {
- placeable.place(0, scroll.roundToInt())
- }
+ layout(placeable.width, squishedHeight) { placeable.place(0, scroll) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 28bf47400c4d..d4f82983469b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -43,4 +43,10 @@ constructor(
SharingStarted.WhileSubscribed(),
paginatedGridInteractor.defaultRows,
)
+
+ /*
+ * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
+ * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
+ */
+ var inFirstPage = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
index 625459d1c6fa..2425f137f1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt
@@ -25,7 +25,10 @@ object ElementKeys {
val GridAnchor = ElementKey("QuickSettingsGridAnchor")
val FooterActions = ElementKey("FooterActions")
- class TileElementKey(spec: TileSpec, val position: Int) : ElementKey(spec.spec, spec.spec)
+ fun TileSpec.toElementKey(positionInGrid: Int) =
+ ElementKey(this.spec, TileIdentity(this, positionInGrid))
- fun TileSpec.toElementKey(positionInGrid: Int) = TileElementKey(this, positionInGrid)
+ val TileElementMatcher = ElementKey.withIdentity { it is TileIdentity }
}
+
+private data class TileIdentity(val spec: TileSpec, val position: Int)
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 a4fe4e3e1243..ad76b4f21bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -169,50 +169,34 @@ public class DndTile extends QSTileImpl<BooleanState> {
private void enableZenMode(@Nullable Expandable expandable) {
int zenDuration = mSettingZenDuration.getValue();
- boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
- if (showOnboarding) {
- // don't show on-boarding again or notification ever
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- // turn on DND
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- // show on-boarding screen
- Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
- } else {
- switch (zenDuration) {
- case Settings.Secure.ZEN_DURATION_PROMPT:
- mUiHandler.post(() -> {
- Dialog dialog = makeZenModeDialog();
- if (expandable != null) {
- DialogTransitionAnimator.Controller controller =
- expandable.dialogTransitionController(new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
- if (controller != null) {
- mDialogTransitionAnimator.show(dialog,
- controller, /* animateBackgroundBoundsChange= */ false);
- } else {
- dialog.show();
- }
+ switch (zenDuration) {
+ case Settings.Secure.ZEN_DURATION_PROMPT:
+ mUiHandler.post(() -> {
+ Dialog dialog = makeZenModeDialog();
+ if (expandable != null) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ false);
} else {
dialog.show();
}
- });
- break;
- case Settings.Secure.ZEN_DURATION_FOREVER:
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- break;
- default:
- Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- mHost.getUserId(), true).id;
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- conditionId, TAG);
- }
+ } else {
+ dialog.show();
+ }
+ });
+ break;
+ case Settings.Secure.ZEN_DURATION_FOREVER:
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ break;
+ default:
+ Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
+ mHost.getUserId(), true).id;
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ conditionId, TAG);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index a89f752fe212..4beec1041a03 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -44,6 +44,7 @@ import dagger.multibindings.IntoMap
[
BouncerSceneModule::class,
CommunalSceneModule::class,
+ DreamSceneModule::class,
EmptySceneModule::class,
GoneSceneModule::class,
LockscreenSceneModule::class,
@@ -98,6 +99,7 @@ interface SceneContainerFrameworkModule {
listOfNotNull(
Scenes.Gone,
Scenes.Communal,
+ Scenes.Dream,
Scenes.Lockscreen,
Scenes.Bouncer,
Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
@@ -114,9 +116,10 @@ interface SceneContainerFrameworkModule {
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
- Scenes.Shade to 2.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 3.takeUnless { DualShade.isEnabled },
- Scenes.Bouncer to 4,
+ Scenes.Dream to 2,
+ Scenes.Shade to 3.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings to 4.takeUnless { DualShade.isEnabled },
+ Scenes.Bouncer to 5,
)
.filterValues { it != null }
.mapValues { checkNotNull(it.value) },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
deleted file mode 100644
index a8d077777121..000000000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepository.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.repository
-
-import android.graphics.Region
-import android.view.ISystemGestureExclusionListener
-import android.view.IWindowManager
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class SystemGestureExclusionRepository
-@Inject
-constructor(private val windowManager: IWindowManager) {
-
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return conflatedCallbackFlow {
- val listener =
- object : ISystemGestureExclusionListener.Stub() {
- override fun onSystemGestureExclusionChanged(
- displayId: Int,
- restrictedRegion: Region?,
- unrestrictedRegion: Region?,
- ) {
- trySend(restrictedRegion)
- }
- }
- windowManager.registerSystemGestureExclusionListener(listener, displayId)
-
- awaitClose {
- windowManager.unregisterSystemGestureExclusionListener(listener, displayId)
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index 82b4b1c57f7e..16492efa658a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -33,6 +33,9 @@ object Scenes {
/** The communal scene shows the glanceable hub when device is locked and docked. */
@JvmField val Communal = SceneKey("communal")
+ /** The dream scene shows up when a dream activity is showing. */
+ @JvmField val Dream = SceneKey("dream")
+
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
* content from the scene framework.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index a8be5804d04a..38f4e73d3234 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -107,13 +107,7 @@ object SceneWindowRootViewBinder {
view.viewModel(
traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = {
- viewModelFactory.create(
- view,
- view.context.displayId,
- motionEventHandlerReceiver,
- )
- },
+ factory = { viewModelFactory.create(view, motionEventHandlerReceiver) },
) { viewModel ->
try {
view.setViewTreeOnBackPressedDispatcherOwner(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index ea19020d84d4..f0f476e65e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,6 +25,7 @@ import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.core.view.updateMargins
+import com.android.systemui.Flags
import com.android.systemui.compose.ComposeInitializer
import com.android.systemui.res.R
@@ -103,6 +104,8 @@ open class WindowRootView(
private fun applyMargins() {
val count = childCount
+ val hasFlagsEnabled = Flags.checkLockscreenGoneTransition()
+ var hasChildMarginUpdated = false
for (i in 0 until count) {
val child = getChildAt(i)
if (child.layoutParams is LayoutParams) {
@@ -113,10 +116,17 @@ open class WindowRootView(
layoutParams.leftMargin != leftInset)
) {
layoutParams.updateMargins(left = leftInset, right = rightInset)
- child.requestLayout()
+ hasChildMarginUpdated = true
+ if (!hasFlagsEnabled) {
+ child.requestLayout()
+ }
}
}
}
+ if (hasFlagsEnabled && hasChildMarginUpdated) {
+ // Request layout at once after all children's margins has updated
+ requestLayout()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
deleted file mode 100644
index a1d915a658ec..000000000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerGestureFilter.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.viewmodel
-
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.scene.domain.interactor.SystemGestureExclusionInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlin.math.roundToInt
-
-/** Decides whether drag gestures should be filtered out in the scene container framework. */
-class SceneContainerGestureFilter
-@AssistedInject
-constructor(interactor: SystemGestureExclusionInteractor, @Assisted displayId: Int) :
- ExclusiveActivatable() {
-
- private val hydrator = Hydrator("SceneContainerGestureFilter.hydrator")
- private val exclusionRegion by
- hydrator.hydratedStateOf(
- traceName = "exclusionRegion",
- initialValue = null,
- source = interactor.exclusionRegion(displayId),
- )
-
- override suspend fun onActivated(): Nothing {
- hydrator.activate()
- }
-
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- check(isActive) { "Must be activated to use!" }
-
- return exclusionRegion?.contains(startPosition.x.roundToInt(), startPosition.y.roundToInt())
- ?: false
- }
-
- @AssistedFactory
- interface Factory {
- fun create(displayId: Int): SceneContainerGestureFilter
- }
-}
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 f5053853846c..889380a4ddbf 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
@@ -19,7 +19,6 @@ package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
import android.view.View
import androidx.compose.runtime.getValue
-import androidx.compose.ui.geometry.Offset
import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
@@ -61,10 +60,8 @@ constructor(
shadeInteractor: ShadeInteractor,
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
- gestureFilterFactory: SceneContainerGestureFilter.Factory,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@Assisted view: View,
- @Assisted displayId: Int,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -92,8 +89,6 @@ constructor(
},
)
- private val gestureFilter: SceneContainerGestureFilter = gestureFilterFactory.create(displayId)
-
override suspend fun onActivated(): Nothing {
try {
// Sends a MotionEventHandler to the owner of the view-model so they can report
@@ -112,7 +107,6 @@ constructor(
coroutineScope {
launch { hydrator.activate() }
- launch { gestureFilter.activate() }
launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
}
awaitCancellation()
@@ -262,17 +256,6 @@ constructor(
}
}
- /**
- * Returns `true` if a drag gesture starting at [startPosition] should be filtered out (e.g.
- * ignored, `false` otherwise.
- *
- * Invoke this and pass in the position of the `ACTION_DOWN` pointer event that began the
- * gesture.
- */
- fun shouldFilterGesture(startPosition: Offset): Boolean {
- return gestureFilter.shouldFilterGesture(startPosition)
- }
-
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
@@ -289,7 +272,6 @@ constructor(
interface Factory {
fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
): SceneContainerViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
index 6730d2d86d5f..7b566885c092 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
@@ -49,7 +49,7 @@ constructor(
override fun handleScreenshot(
screenshot: ScreenshotData,
finisher: Consumer<Uri?>,
- requestCallback: TakeScreenshotService.RequestCallback
+ requestCallback: TakeScreenshotService.RequestCallback,
) {
if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null)
@@ -69,8 +69,8 @@ constructor(
Executors.newSingleThreadExecutor(),
UUID.randomUUID(),
screenshot.bitmap,
- screenshot.getUserOrDefault(),
- screenshot.displayId
+ screenshot.userHandle,
+ screenshot.displayId,
)
future.addListener(
{
@@ -86,7 +86,7 @@ constructor(
requestCallback.reportError()
}
},
- mainExecutor
+ mainExecutor,
)
}
@@ -98,11 +98,11 @@ constructor(
.notifyScreenshotError(R.string.screenshot_failed_to_save_text)
} else {
uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString)
- if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) {
+ if (userManager.isManagedProfile(screenshot.userHandle.identifier)) {
uiEventLogger.log(
ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE,
0,
- screenshot.packageNameString
+ screenshot.packageNameString,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index 7724abd4aaac..e58960072454 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -301,7 +301,7 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler
saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
if (result.uri != null) {
ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
- result.uri, screenshot.getUserOrDefault(), result.timestamp);
+ result.uri, screenshot.getUserHandle(), result.timestamp);
mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index 29208f89c4e1..0806be8d6bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -214,11 +214,7 @@ internal constructor(
saveScreenshotInBackground(screenshot, requestId, finisher) { result ->
if (result.uri != null) {
val savedScreenshot =
- ScreenshotSavedResult(
- result.uri,
- screenshot.getUserOrDefault(),
- result.timestamp,
- )
+ ScreenshotSavedResult(result.uri, screenshot.userHandle, result.timestamp)
actionsController.setCompletedScreenshot(requestId, savedScreenshot)
}
}
@@ -235,7 +231,7 @@ internal constructor(
window.setFocusable(true)
viewProxy.requestFocus()
- enqueueScrollCaptureRequest(requestId, screenshot.userHandle!!)
+ enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
window.attachWindow()
@@ -267,7 +263,7 @@ internal constructor(
private fun prepareViewForNewScreenshot(screenshot: ScreenshotData, oldPackageName: String?) {
window.whenWindowAttached {
- announcementResolver.getScreenshotAnnouncement(screenshot.userHandle!!.identifier) {
+ announcementResolver.getScreenshotAnnouncement(screenshot.userHandle.identifier) {
viewProxy.announceForAccessibility(it)
}
}
@@ -517,7 +513,7 @@ internal constructor(
bgExecutor,
requestId,
screenshot.bitmap,
- screenshot.getUserOrDefault(),
+ screenshot.userHandle,
display.displayId,
)
future.addListener(
@@ -525,7 +521,7 @@ internal constructor(
try {
val result = future.get()
Log.d(TAG, "Saved screenshot: $result")
- logScreenshotResultStatus(result.uri, screenshot.userHandle!!)
+ logScreenshotResultStatus(result.uri, screenshot.userHandle)
onResult.accept(result)
if (LogConfig.DEBUG_CALLBACK) {
Log.d(TAG, "finished bg processing, calling back with uri: ${result.uri}")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index b5d45a488997..2df1e8aa2e68 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -15,17 +15,17 @@ import com.android.internal.util.ScreenshotRequest
/** [ScreenshotData] represents the current state of a single screenshot being acquired. */
data class ScreenshotData(
- @ScreenshotType var type: Int,
- @ScreenshotSource var source: Int,
+ @ScreenshotType val type: Int,
+ @ScreenshotSource val source: Int,
/** UserHandle for the owner of the app being screenshotted, if known. */
- var userHandle: UserHandle?,
+ val userHandle: UserHandle,
/** ComponentName of the top-most app in the screenshot. */
- var topComponent: ComponentName?,
+ val topComponent: ComponentName?,
var screenBounds: Rect?,
- var taskId: Int,
+ val taskId: Int,
var insets: Insets,
var bitmap: Bitmap?,
- var displayId: Int,
+ val displayId: Int,
) {
val packageNameString
get() = topComponent?.packageName ?: ""
@@ -40,7 +40,7 @@ data class ScreenshotData(
ScreenshotData(
type = request.type,
source = request.source,
- userHandle = if (request.userId >= 0) UserHandle.of(request.userId) else null,
+ userHandle = UserHandle.of(request.userId),
topComponent = request.topComponent,
screenBounds = request.boundsInScreen,
taskId = request.taskId,
@@ -50,16 +50,21 @@ data class ScreenshotData(
)
@VisibleForTesting
- fun forTesting() =
+ fun forTesting(
+ userHandle: UserHandle = UserHandle.CURRENT,
+ source: Int = ScreenshotSource.SCREENSHOT_KEY_CHORD,
+ topComponent: ComponentName? = null,
+ bitmap: Bitmap? = null,
+ ) =
ScreenshotData(
type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- source = ScreenshotSource.SCREENSHOT_KEY_CHORD,
- userHandle = null,
- topComponent = null,
+ source = source,
+ userHandle = userHandle,
+ topComponent = topComponent,
screenBounds = null,
taskId = 0,
insets = Insets.NONE,
- bitmap = null,
+ bitmap = bitmap,
displayId = Display.DEFAULT_DISPLAY,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 38608d0e793a..a7557463b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -28,6 +28,7 @@ import com.android.systemui.Flags.screenshotMultidisplayFocusChange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
@@ -83,6 +84,7 @@ constructor(
private val uiEventLogger: UiEventLogger,
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
private val headlessScreenshotHandler: HeadlessScreenshotHandler,
+ private val focusedDisplayRepository: FocusedDisplayRepository,
) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
private var screenshotController: InteractiveScreenshotHandler? = null
@@ -202,10 +204,10 @@ constructor(
// Return the single display to be screenshot based upon the request.
private suspend fun getDisplayToScreenshot(screenshotRequest: ScreenshotRequest): Display {
return when (screenshotRequest.source) {
- // TODO(b/367394043): Overview requests should use a display ID provided in
- // ScreenshotRequest.
ScreenshotSource.SCREENSHOT_OVERVIEW ->
- displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
+ // Show on the display where overview was shown if available.
+ displayRepository.getDisplay(screenshotRequest.displayId)
+ ?: displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
?: error("Can't find default display")
// Key chord and vendor gesture occur on the device itself, so screenshot the device's
@@ -216,14 +218,13 @@ constructor(
?: error("Can't find default display")
// All other invocations use the focused display
- else -> focusedDisplay()
+ else ->
+ displayRepository.getDisplay(focusedDisplayRepository.focusedDisplayId.value)
+ ?: displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
+ ?: error("Can't find default display")
}
}
- // TODO(b/367394043): Determine the focused display here.
- private suspend fun focusedDisplay() =
- displayRepository.getDisplay(Display.DEFAULT_DISPLAY) ?: error("Can't find default display")
-
/** Propagates the close system dialog signal to the ScreenshotController. */
override fun onCloseSystemDialogsReceived() {
if (screenshotController?.isPendingSharedTransition() == false) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index b3d5c9e9691c..b67ad8a2b947 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -92,7 +92,7 @@ class PolicyRequestProcessor(
updates.component,
updates.owner,
type.taskId,
- type.taskBounds
+ type.taskBounds,
)
is FullScreen ->
replaceWithScreenshot(
@@ -122,7 +122,7 @@ class PolicyRequestProcessor(
componentName = topMainRootTask?.topActivity ?: defaultComponent,
taskId = topMainRootTask?.taskId,
owner = defaultOwner,
- displayId = original.displayId
+ displayId = original.displayId,
)
}
@@ -141,14 +141,14 @@ class PolicyRequestProcessor(
userHandle = owner,
taskId = taskId,
topComponent = componentName,
- screenBounds = taskBounds
+ screenBounds = taskBounds,
)
}
private suspend fun replaceWithScreenshot(
original: ScreenshotData,
componentName: ComponentName?,
- owner: UserHandle?,
+ owner: UserHandle,
displayId: Int,
taskId: Int? = null,
): ScreenshotData {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index a4906c12b487..c56ce768df41 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -41,7 +41,7 @@ import kotlin.math.sign
class ScreenshotAnimationController(
private val view: ScreenshotShelfView,
- private val viewModel: ScreenshotViewModel
+ private val viewModel: ScreenshotViewModel,
) {
private var animator: Animator? = null
private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
@@ -56,7 +56,7 @@ class ScreenshotAnimationController(
listOf<View>(
view.requireViewById(R.id.screenshot_preview_border),
view.requireViewById(R.id.screenshot_badge),
- view.requireViewById(R.id.screenshot_dismiss_button)
+ view.requireViewById(R.id.screenshot_dismiss_button),
)
private val fadeUI =
listOf<View>(
@@ -70,9 +70,11 @@ class ScreenshotAnimationController(
fun getEntranceAnimation(
bounds: Rect,
showFlash: Boolean,
- onRevealMilestone: () -> Unit
+ onRevealMilestone: () -> Unit,
): Animator {
val entranceAnimation = AnimatorSet()
+ view.alpha = 1f
+ view.translationX = 0f
val previewAnimator = getPreviewAnimator(bounds)
@@ -142,7 +144,7 @@ class ScreenshotAnimationController(
fun runLongScreenshotTransition(
destRect: Rect,
longScreenshot: ScrollCaptureController.LongScreenshot,
- onTransitionEnd: Runnable
+ onTransitionEnd: Runnable,
): Animator {
val animSet = AnimatorSet()
@@ -165,7 +167,7 @@ class ScreenshotAnimationController(
matrix.setScale(currentScale, currentScale)
matrix.postTranslate(
longScreenshot.left * currentScale,
- longScreenshot.top * currentScale
+ longScreenshot.top * currentScale,
)
scrollTransitionPreview.setImageMatrix(matrix)
val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4f47536f6b32..f83548ddbf45 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -994,7 +994,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
// be dropped, causing the shade expansion to fail silently. Since the shade doesn't open,
// it doesn't become visible, and the bounds will never update. Therefore, we must detect
// the incorrect bounds here and force the update so that touches are routed correctly.
- if (SceneContainerFlag.isEnabled() && mWindowRootView.getVisibility() == View.INVISIBLE) {
+ if (SceneContainerFlag.isEnabled()
+ && mWindowRootView != null
+ && mWindowRootView.getVisibility() == View.INVISIBLE) {
Rect bounds = newConfig.windowConfiguration.getBounds();
if (mWindowRootView.getWidth() != bounds.width()) {
mLogger.logConfigChangeWidthAdjust(mWindowRootView.getWidth(), bounds.width());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index bbcf10be5ff0..5b8277284117 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -13,10 +13,9 @@ per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@goo
per-file *Repository* = set noparent
per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
-per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
-per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
-per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index 65b6231705d4..e5f684635ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -30,41 +30,47 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
fun singleShadeActions(
requireTwoPointersForTopEdgeForQs: Boolean = false
): Array<Pair<UserAction, UserActionResult>> {
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
+ val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to Scenes.Shade,
- swipeDown(pointerCount = 2) to Scenes.Shade,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge.
swipeDownFromTop(pointerCount = 1) to
if (requireTwoPointersForTopEdgeForQs) {
- Scenes.Shade
+ shadeUserActionResult
} else {
- Scenes.QuickSettings
+ qsSceneUserActionResult
},
- swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+ swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
- Swipe.Down to splitShadeSceneKey,
- swipeDown(pointerCount = 2) to splitShadeSceneKey,
+ Swipe.Down to shadeUserActionResult,
+ swipeDown(pointerCount = 2) to shadeUserActionResult,
// Swiping down from the top edge goes to QS.
- swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
- swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
+ swipeDownFromTop(pointerCount = 1) to shadeUserActionResult,
+ swipeDownFromTop(pointerCount = 2) to shadeUserActionResult,
)
}
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+ val notifShadeUserActionResult =
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ val qsShadeuserActionResult =
+ UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
return arrayOf(
- Swipe.Down to Overlays.NotificationsShade,
+ Swipe.Down to notifShadeUserActionResult,
Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
- Overlays.QuickSettingsShade,
+ qsShadeuserActionResult,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index cc6e8c246ff7..3113dc462e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -32,6 +32,7 @@ import com.android.systemui.shade.shared.model.ShadeMode
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
/**
* Models the UI state for the user actions that the user can perform to navigate to other scenes.
@@ -50,7 +51,9 @@ constructor(
combine(
shadeInteractor.shadeMode,
qsSceneAdapter.isCustomizerShowing,
- sceneBackInteractor.backScene.map { it ?: SceneFamilies.Home },
+ sceneBackInteractor.backScene
+ .filter { it != Scenes.Shade }
+ .map { it ?: SceneFamilies.Home },
) { shadeMode, isCustomizerShowing, backScene ->
buildMap<UserAction, UserActionResult> {
if (!isCustomizerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 7244f8a64c19..a79b78f8d5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -58,15 +58,18 @@ import androidx.annotation.WorkerThread;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -286,6 +289,8 @@ public class NotificationLockscreenUserManagerImpl implements
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
+ private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+
@Inject
public NotificationLockscreenUserManagerImpl(Context context,
BroadcastDispatcher broadcastDispatcher,
@@ -305,7 +310,8 @@ public class NotificationLockscreenUserManagerImpl implements
SecureSettings secureSettings,
DumpManager dumpManager,
LockPatternUtils lockPatternUtils,
- FeatureFlagsClassic featureFlags) {
+ FeatureFlagsClassic featureFlags,
+ Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy) {
mContext = context;
mMainExecutor = mainExecutor;
mBackgroundExecutor = backgroundExecutor;
@@ -325,6 +331,7 @@ public class NotificationLockscreenUserManagerImpl implements
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
mFeatureFlags = featureFlags;
+ mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
@@ -748,8 +755,13 @@ public class NotificationLockscreenUserManagerImpl implements
// camera on the keyguard has a state of SHADE but the keyguard is still showing.
final boolean showingKeyguard = mState != StatusBarState.SHADE
|| mKeyguardStateController.isShowing();
- final boolean devicePublic = showingKeyguard && mKeyguardStateController.isMethodSecure();
-
+ final boolean devicePublic;
+ if (SceneContainerFlag.isEnabled()) {
+ devicePublic = !mDeviceUnlockedInteractorLazy.get()
+ .getDeviceUnlockStatus().getValue().isUnlocked();
+ } else {
+ devicePublic = showingKeyguard && mKeyguardStateController.isMethodSecure();
+ }
// Look for public mode users. Users are considered public in either case of:
// - device keyguard is shown in secure mode;
@@ -802,11 +814,17 @@ public class NotificationLockscreenUserManagerImpl implements
private void notifyNotificationStateChanged() {
if (!Looper.getMainLooper().isCurrentThread()) {
- mMainExecutor.execute(() -> {
+ if (Flags.checkLockscreenGoneTransition()) {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
- listener.onNotificationStateChanged();
+ mMainExecutor.execute(listener::onNotificationStateChanged);
}
- });
+ } else {
+ mMainExecutor.execute(() -> {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
+ });
+ }
} else {
for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
listener.onNotificationStateChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 5d14be8c974c..73ad0e50793a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -272,7 +272,7 @@ public class StatusBarStateControllerImpl implements
* Updates the {@link StatusBarState} and notifies registered listeners, if needed.
*/
private void updateStateAndNotifyListeners(int state) {
- if (state != mUpcomingState) {
+ if (state != mUpcomingState && !SceneContainerFlag.isEnabled()) {
Log.d(TAG, "setState: requested state " + StatusBarState.toString(state)
+ "!= upcomingState: " + StatusBarState.toString(mUpcomingState) + ". "
+ "This usually means the status bar state transition was interrupted before "
@@ -728,20 +728,23 @@ public class StatusBarStateControllerImpl implements
// doesn't work well for clients of this class (like remote input) that expect the device to
// be fully and properly unlocked when the state changes to SHADE.
//
- // Therefore, we calculate the device to be in a locked-ish state (KEYGUARD or SHADE_LOCKED,
+ // Therefore, we consider the device to be in a keyguardish state (KEYGUARD or SHADE_LOCKED,
// but not SHADE) if *any* of these are still true:
// 1. deviceUnlockStatus.isUnlocked is false.
- // 2. We are on (currentScene equals) a locked-ish scene (Lockscreen, Bouncer, or Communal).
- // 3. We are over (backStack contains) a locked-ish scene (Lockscreen or Communal).
+ // 2. currentScene is a keyguardish scene (Lockscreen, Bouncer, or Communal).
+ // 3. backStack contains a keyguardish scene (Lockscreen or Communal).
+
+ final boolean onKeyguardish = onLockscreen || onBouncer || onCommunal;
+ final boolean overKeyguardish = overLockscreen || overCommunal;
if (isOccluded) {
// Occlusion is special; even though the device is still technically on the lockscreen,
// the UI behaves as if it is unlocked.
newState = StatusBarState.SHADE;
- } else if (onLockscreen || onBouncer || onCommunal || overLockscreen || overCommunal) {
- // We get here if we are on or over a locked-ish scene, even if isUnlocked is true; we
+ } else if (onKeyguardish || overKeyguardish) {
+ // We get here if we are on or over a keyguardish scene, even if isUnlocked is true; we
// want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
- // locked-ish scene.
+ // keyguardish scene.
if (onShade || onQuickSettings || overShade || overlaidShade || overlaidQuickSettings) {
newState = StatusBarState.SHADE_LOCKED;
} else {
@@ -751,7 +754,7 @@ public class StatusBarStateControllerImpl implements
newState = StatusBarState.SHADE;
} else if (onShade || onQuickSettings) {
// We get here if deviceUnlockStatus.isUnlocked is false but we are no longer on or over
- // a locked-ish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
+ // a keyguardish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
newState = StatusBarState.SHADE_LOCKED;
} else {
throw new IllegalArgumentException(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index e3c47a43aad4..321593b6ada4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -596,7 +596,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
// NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to
// distinguish it from VCN over Cellular.
if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(mConnectivityManager, networkCapabilities)
+ != null) {
transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI;
break;
}
@@ -1112,7 +1113,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
continue;
}
if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
+ && Utils.tryGetWifiInfoForVcn(
+ mConnectivityManager, mLastDefaultNetworkCapabilities)
+ != null) {
mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 97add30b9a57..a24f2672f251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -91,7 +91,6 @@ class LockscreenSmartspaceController
constructor(
private val context: Context,
private val featureFlags: FeatureFlags,
- private val smartspaceManager: SmartspaceManager?,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
private val systemClock: SystemClock,
@@ -124,6 +123,7 @@ constructor(
private const val MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP = 5
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
@@ -461,7 +461,11 @@ constructor(
}
private fun connectSession() {
- if (smartspaceManager == null) return
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) return
if (datePlugin == null && weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return
@@ -474,12 +478,14 @@ constructor(
return
}
- val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(
- context, BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD).build())
+ val newSession = userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(
+ userTracker.userContext, BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD
+ ).build()
+ )
Log.d(TAG, "Starting smartspace session for " +
BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
deviceProvisionedController.removeCallback(deviceProvisionedListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index 5558ab1e2af2..0a7f08d5860b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -2,11 +2,13 @@ set noparent
# Bug component: 78010
+jeffdq@google.com
+juliacr@google.com
+
aioana@google.com
aroederer@google.com
+asc@google.com
iyz@google.com
-jeffdq@google.com
-juliacr@google.com
juliatuttle@google.com
kurucz@google.com
liuyining@google.com
@@ -15,4 +17,4 @@ matiashe@google.com
valiiftime@google.com
yurilin@google.com
-per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
+per-file MediaNotificationProcessor.java = ethibodeau@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 41419f31eb7a..8660cd117493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -22,15 +22,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
@@ -85,6 +88,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private boolean mNotifPanelLaunchingActivity;
private boolean mCommunalShowing = false;
private boolean mLockscreenShowing = false;
+ private boolean mLockscreenInGoneTransition = false;
private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
@@ -158,6 +162,13 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
KeyguardState.LOCKSCREEN),
this::onLockscreenKeyguardStateTransitionValueChanged);
}
+ if (Flags.checkLockscreenGoneTransition()) {
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
+ Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
+ this::onLockscreenInGoneTransitionChanged);
+ }
+
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -239,7 +250,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private void updateAllowedStates(String field, boolean value) {
boolean wasPipelineRunAllowed = mPipelineRunAllowed;
boolean wasReorderingAllowed = mReorderingAllowed;
- mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
+ // No need to run notification pipeline when the lockscreen is in fading animation.
+ mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
+ || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
mReorderingAllowed = isReorderingAllowed();
if (wasPipelineRunAllowed != mPipelineRunAllowed
|| wasReorderingAllowed != mReorderingAllowed) {
@@ -330,7 +343,6 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
updateAllowedStates("fullyDozed", fullyDozed);
}
};
-
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
@@ -353,6 +365,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
+ if (Flags.checkLockscreenGoneTransition()) {
+ pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
+ }
pw.println("reorderingAllowed: " + mReorderingAllowed);
pw.println(" sleepy: " + mSleepy);
pw.println(" fullyDozed: " + mFullyDozed);
@@ -401,4 +416,15 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
mLockscreenShowing = isShowing;
updateAllowedStates("lockscreenShowing", isShowing);
}
+
+ private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
+ if (!Flags.checkLockscreenGoneTransition()) {
+ return;
+ }
+ if (inGoneTransition == mLockscreenInGoneTransition) {
+ return;
+ }
+ mLockscreenInGoneTransition = inGoneTransition;
+ updateAllowedStates("lockscreenInGoneTransition", mLockscreenInGoneTransition);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index aa203d7c0aa9..e25127e3e0d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -39,10 +39,10 @@ class HeadsUpNotificationInteractor
@Inject
constructor(
private val headsUpRepository: HeadsUpRepository,
- private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
- private val shadeInteractor: ShadeInteractor,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ shadeInteractor: ShadeInteractor,
) {
/** The top-ranked heads up row, regardless of pinned state */
@@ -56,8 +56,7 @@ constructor(
}
.distinctUntilChanged()
- /** Set of currently pinned top-level heads up rows to be displayed. */
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ private val activeHeadsUpRows: Flow<Set<Pair<HeadsUpRowKey, Boolean>>> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
@@ -67,9 +66,7 @@ constructor(
repositories.map { repo ->
repo.isPinned.map { isPinned -> repo to isPinned }
}
- combine(toCombine) { pairs ->
- pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
- }
+ combine(toCombine) { pairs -> pairs.toSet() }
} else {
// if the set is empty, there are no flows to combine
flowOf(emptySet())
@@ -78,6 +75,26 @@ constructor(
}
}
+ /** Set of currently active top-level heads up rows to be displayed. */
+ val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() }
+ }
+ }
+
+ /** Set of currently pinned top-level heads up rows to be displayed. */
+ val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ activeHeadsUpRows.map {
+ it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ }
+ }
+ }
+
/** Are there any pinned heads up rows to display? */
val hasPinnedRows: Flow<Boolean> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 8c80fd400360..36e3e92e4063 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -203,13 +203,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
messagingStyle = mConversationProcessor
.processNotification(entry, builder, mLogger);
}
- result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ SingleLineViewModel viewModel = SingleLineViewInflater
.inflateSingleLineViewModel(
entry.getSbn().getNotification(),
messagingStyle,
builder,
row.getContext()
);
+ // If the messagingStyle is null, we want to inflate the normal view
+ isConversation = viewModel.isConversation();
+ result.mInflatedSingleLineViewModel = viewModel;
result.mInflatedSingleLineView =
SingleLineViewInflater.inflatePrivateSingleLineView(
isConversation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
index 3b0f1ee22be3..a17197c1f8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -24,14 +24,14 @@ import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineVi
object SingleLineViewBinder {
@JvmStatic
fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
- if (viewModel?.isConversation() == true && view is HybridConversationNotificationView) {
+ if (view is HybridConversationNotificationView) {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
- viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+ viewModel?.conversationData?.avatar?.let { view.setAvatar(it) }
view.setText(
- viewModel.titleText,
- viewModel.contentText,
- viewModel.conversationData?.conversationSenderName
+ viewModel?.titleText,
+ viewModel?.contentText,
+ viewModel?.conversationData?.conversationSenderName,
)
} else {
// bind the title and content text views
@@ -39,7 +39,7 @@ object SingleLineViewBinder {
bind(
/* title = */ viewModel?.titleText,
/* text = */ viewModel?.contentText,
- /* contentView = */ null
+ /* contentView = */ null,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 935e2a37b13c..38390e7bdb39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -356,11 +356,23 @@ constructor(
}
}
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
- headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
+ headsUpNotificationInteractor.activeHeadsUpRowKeys.dumpWhileCollecting(
+ "pinnedHeadsUpRows"
+ )
+ }
+ }
+
+ val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ headsUpNotificationInteractor.pinnedHeadsUpRowKeys.dumpWhileCollecting(
+ "pinnedHeadsUpRows"
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index c9eaec7c5b85..aec81b0241a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -84,9 +84,9 @@ constructor(
private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
// The lockscreen stack is visible during all transitions away from the lockscreen, so keep
// the stack expanded until those transitions finish.
- return if (change.isFrom({ it == Scenes.Lockscreen }, to = { true })) {
+ return if (change.isTransitioning(from = Scenes.Lockscreen)) {
true
- } else if (change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })) {
+ } else if (change.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)) {
false
} else {
(expandedInScene(change.fromScene) && expandedInScene(change.toScene))
@@ -101,11 +101,11 @@ constructor(
return if (fullyExpandedDuringSceneChange(change)) {
1f
} else if (
- change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade }) ||
- change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })
+ change.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+ change.isTransitioning(from = Scenes.Gone, to = Scenes.Lockscreen)
) {
shadeExpansion
- } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
+ } else if (change.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings)) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
(qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
@@ -213,7 +213,11 @@ constructor(
private val qsAllowsClipping: Flow<Boolean> =
combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
- qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+ when (shadeMode) {
+ is ShadeMode.Dual -> false
+ is ShadeMode.Split -> true
+ is ShadeMode.Single -> qsExpansion < 0.5f
+ }
}
.distinctUntilChanged()
@@ -325,9 +329,3 @@ constructor(
fun create(): NotificationScrollViewModel
}
}
-
-private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
- (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
-
-private fun ChangeScene.isFrom(from: (SceneKey) -> Boolean, to: (SceneKey) -> Boolean): Boolean =
- from(fromScene) && to(toScene)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index dc15970b318b..e2e5c5970ff5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -26,6 +26,7 @@ import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class HeadsUpNotificationViewBinder
@@ -35,18 +36,21 @@ constructor(private val viewModel: NotificationListViewModel) {
coroutineScope {
launch {
var previousKeys = emptySet<HeadsUpRowKey>()
- viewModel.pinnedHeadsUpRows
+ combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
- val added = newKeys - previousKeys
- val removed = previousKeys - newKeys
- previousKeys = newKeys
+ val pinned = newKeys.first
+ val all = newKeys.second
+ val added = all.union(pinned) - previousKeys
+ val removed = previousKeys - pinned
+ previousKeys = pinned
+ Pair(added, removed)
if (animationsEnabled) {
added.forEach { key ->
parentView.generateHeadsUpAnimation(
obtainView(key),
- /* isHeadsUp = */ true
+ /* isHeadsUp = */ true,
)
}
removed.forEach { key ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5f4f72f293a6..0474344ee390 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -594,7 +594,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
- private final GlanceableHubContainerController mGlanceableHubContainerController;
+
+ // Only use before the scene container. Null if scene container is enabled.
+ @Nullable private final GlanceableHubContainerController mGlanceableHubContainerController;
private final EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@@ -807,7 +809,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
- mGlanceableHubContainerController = glanceableHubContainerController;
+ if (!SceneContainerFlag.isEnabled()) {
+ mGlanceableHubContainerController = glanceableHubContainerController;
+ } else {
+ mGlanceableHubContainerController = null;
+ }
mEmergencyGestureIntentFactory = emergencyGestureIntentFactory;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -2972,7 +2978,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void handleCommunalHubTouch(MotionEvent event) {
- mGlanceableHubContainerController.onTouchEvent(event);
+ if (mGlanceableHubContainerController != null) {
+ mGlanceableHubContainerController.onTouchEvent(event);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8c0353813ec6..f8eae3607f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -411,7 +411,8 @@ public class PhoneStatusBarPolicy
mainActiveMode.getIcon().key().resPackage(),
mainActiveMode.getIcon().key().resId(),
mainActiveMode.getIcon().drawable(),
- mainActiveMode.getName(),
+ mResources.getString(R.string.active_mode_content_description,
+ mainActiveMode.getName()),
StatusBarIcon.Shape.FIXED_SPACE);
}
if (visible != mZenVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5ae24f76a9bf..479ffb728eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.DejankUtils;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -1203,6 +1204,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void hide(long startTime, long fadeoutDuration) {
Trace.beginSection("StatusBarKeyguardViewManager#hide");
+ if (Flags.checkLockscreenGoneTransition()) {
+ DejankUtils.notifyRendererOfExpensiveFrame(
+ mNotificationShadeWindowController.getWindowRootView(),
+ "StatusBarKeyguardViewManager#hide");
+ }
mKeyguardStateController.notifyKeyguardState(false,
mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index b1754fd59cee..200f0804e42b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -34,11 +34,15 @@ import android.view.ViewParent;
import androidx.annotation.Nullable;
+import com.android.compose.animation.scene.ObservableTransitionState;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
@@ -52,6 +56,9 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
import java.util.concurrent.Executor;
@@ -80,6 +87,8 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
private final ActionClickLogger mActionClickLogger;
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
+ private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ private final Lazy<SceneInteractor> mSceneInteractorLazy;
/**
*/
@@ -95,7 +104,10 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
ShadeController shadeController,
CommandQueue commandQueue,
ActionClickLogger clickLogger,
- @Main Executor executor) {
+ @Main Executor executor,
+ Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ JavaAdapter javaAdapter) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mShadeController = shadeController;
@@ -113,20 +125,28 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
mActionClickLogger = clickLogger;
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mGroupExpansionManager = groupExpansionManager;
+ mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
+ mSceneInteractorLazy = sceneInteractorLazy;
+
+ if (SceneContainerFlag.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(
+ mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
+ deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
+ javaAdapter.alwaysCollectFlow(
+ mSceneInteractorLazy.get().getTransitionState(),
+ deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
+ }
}
@Override
public void onStateChanged(int state) {
- boolean hasPendingRemoteInput = mPendingRemoteInputView != null;
- if (state == StatusBarState.SHADE
- && (mStatusBarStateController.leaveOpenOnKeyguardHide() || hasPendingRemoteInput)) {
- if (!mStatusBarStateController.isKeyguardRequested()
- && mKeyguardStateController.isUnlocked()) {
- if (hasPendingRemoteInput) {
- mExecutor.execute(mPendingRemoteInputView::callOnClick);
- }
- mPendingRemoteInputView = null;
- }
+ if (mPendingRemoteInputView == null) {
+ return;
+ }
+
+ if (state == StatusBarState.SHADE && canRetryPendingRemoteInput()) {
+ mExecutor.execute(mPendingRemoteInputView::callOnClick);
+ mPendingRemoteInputView = null;
}
}
@@ -320,6 +340,23 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
}
}
+ /**
+ * Returns {@code true} if it is safe to retry a pending remote input. The exact criteria for
+ * this vary depending whether the scene container is enabled.
+ */
+ private boolean canRetryPendingRemoteInput() {
+ if (SceneContainerFlag.isEnabled()) {
+ final boolean isUnlocked = mDeviceUnlockedInteractorLazy.get()
+ .getDeviceUnlockStatus().getValue().isUnlocked();
+ final boolean isIdle = mSceneInteractorLazy.get()
+ .getTransitionState().getValue() instanceof ObservableTransitionState.Idle;
+ return isUnlocked && isIdle;
+ } else {
+ return mKeyguardStateController.isUnlocked()
+ && !mStatusBarStateController.isKeyguardRequested();
+ }
+ }
+
protected class ChallengeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index eea4c212e40e..9c168be0693f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -95,7 +95,8 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
primaryChipView.show(shouldAnimateChange = true)
is OngoingActivityChipModel.Hidden ->
primaryChipView.hide(
- shouldAnimateChange = primaryChipModel.shouldAnimate
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
new file mode 100644
index 000000000000..441cbb33f821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+/** [DevicePostureController.getDevicePosture] as a [Flow]. */
+@DevicePostureInt
+fun DevicePostureController.devicePosture(): Flow<Int> =
+ conflatedCallbackFlow {
+ val callback = DevicePostureController.Callback { posture -> trySend(posture) }
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+ .onStart { emit(devicePosture) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index da928a364984..3cf206643207 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -32,6 +32,7 @@ import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import com.android.internal.camera.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.ListenerSet;
import java.util.Set;
@@ -41,14 +42,17 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
+ private final @NonNull UserTracker mUserTracker;
private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
private Boolean mRequiresAuthentication;
private final ListenerSet<Callback> mCallbacks = new ListenerSet<>();
public IndividualSensorPrivacyControllerImpl(
- @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ @NonNull SensorPrivacyManager sensorPrivacyManager,
+ @NonNull UserTracker userTracker) {
mSensorPrivacyManager = sensorPrivacyManager;
+ mUserTracker = userTracker;
}
@Override
@@ -94,12 +98,14 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
@Override
public void setSensorBlocked(@Source int source, @Sensor int sensor, boolean blocked) {
- mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked);
+ mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked,
+ mUserTracker.getUserId());
}
@Override
public void suppressSensorPrivacyReminders(int sensor, boolean suppress) {
- mSensorPrivacyManager.suppressSensorPrivacyReminders(sensor, suppress);
+ mSensorPrivacyManager.suppressSensorPrivacyReminders(sensor, suppress,
+ mUserTracker.getUserId());
}
@Override
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 6764839c32d3..4f595ed152e4 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
@@ -76,7 +76,7 @@ constructor(
// can be manually toggled on
mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
// Mode was created as disabled, or disabled by the app that owns it ->
- // will be shown with a "Set up" text
+ // will be shown with a "Not set" text
!mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
else -> false
}
@@ -120,7 +120,7 @@ constructor(
},
onLongClick = { openSettings(mode) },
onLongClickLabel =
- context.resources.getString(R.string.accessibility_long_click_tile)
+ context.resources.getString(R.string.accessibility_long_click_tile),
)
}
}
@@ -137,10 +137,10 @@ constructor(
/**
* Returns a description of the mode, which is:
- * * a prompt to set up the mode if it is not enabled
- * * if it cannot be manually activated, text that says so
- * * otherwise, the trigger description of the mode if it exists...
- * * ...or null if it doesn't
+ * * a prompt to set up the mode if it is not enabled
+ * * if it cannot be manually activated, text that says so
+ * * otherwise, the trigger description of the mode if it exists...
+ * * ...or null if it doesn't
*
* This description is used directly for the content description of a mode tile for screen
* readers, and for the tile subtext will be augmented with the current status of the mode.
@@ -174,7 +174,7 @@ constructor(
context,
R.style.Theme_SystemUI_Dialog,
/* cancelIsNeutral= */ true,
- zenDialogMetricsLogger
+ zenDialogMetricsLogger,
)
.createDialog()
SystemUIDialog.applyFlags(dialog)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 6acc891e93d5..94e19deb0006 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -20,19 +20,27 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
import com.android.systemui.res.R
@@ -47,20 +55,17 @@ fun TutorialSelectionScreen(
Column(
verticalArrangement = Arrangement.Center,
modifier =
- Modifier.background(
- color = MaterialTheme.colorScheme.surfaceContainer,
- )
- .fillMaxSize()
+ Modifier.background(color = MaterialTheme.colorScheme.surfaceContainer).fillMaxSize(),
) {
TutorialSelectionButtons(
onBackTutorialClicked = onBackTutorialClicked,
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
- modifier = Modifier.padding(60.dp)
+ modifier = Modifier.padding(60.dp),
)
DoneButton(
onDoneButtonClicked = onDoneButtonClicked,
- modifier = Modifier.padding(horizontal = 60.dp)
+ modifier = Modifier.padding(horizontal = 60.dp),
)
}
}
@@ -70,30 +75,36 @@ private fun TutorialSelectionButtons(
onBackTutorialClicked: () -> Unit,
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.CenterVertically,
- modifier = modifier
+ modifier = modifier,
) {
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+ icon = Icons.AutoMirrored.Outlined.ArrowBack,
+ iconColor = MaterialTheme.colorScheme.onPrimary,
onClick = onHomeTutorialClicked,
- color = MaterialTheme.colorScheme.primary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.weight(1f),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+ iconColor = MaterialTheme.colorScheme.onTertiary,
onClick = onBackTutorialClicked,
- color = MaterialTheme.colorScheme.tertiary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.tertiary,
+ modifier = Modifier.weight(1f),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
+ iconColor = MaterialTheme.colorScheme.onSecondary,
onClick = onRecentAppsTutorialClicked,
- color = MaterialTheme.colorScheme.secondary,
- modifier = Modifier.weight(1f)
+ backgroundColor = MaterialTheme.colorScheme.secondary,
+ modifier = Modifier.weight(1f),
)
}
}
@@ -101,16 +112,30 @@ private fun TutorialSelectionButtons(
@Composable
private fun TutorialButton(
text: String,
+ icon: ImageVector,
+ iconColor: Color,
onClick: () -> Unit,
- color: Color,
- modifier: Modifier = Modifier
+ backgroundColor: Color,
+ modifier: Modifier = Modifier,
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(16.dp),
- colors = ButtonDefaults.buttonColors(containerColor = color),
- modifier = modifier.aspectRatio(0.66f)
+ colors = ButtonDefaults.buttonColors(containerColor = backgroundColor),
+ modifier = modifier.aspectRatio(0.66f),
) {
- Text(text = text, style = MaterialTheme.typography.headlineLarge)
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = text,
+ modifier = Modifier.width(30.dp).height(30.dp),
+ tint = iconColor,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(text = text, style = MaterialTheme.typography.headlineLarge)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 7c055c8876ae..7f90242fee2e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -135,3 +135,15 @@ fun <T1, T2, T3, T4, T5, R> combineFlows(
): Flow<R> {
return combine(flow, flow2, flow3, flow4, flow5, transform)
}
+
+fun <T1, T2, T3, T4, T5, T6, R> combineFlows(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ transform: (T1, T2, T3, T4, T5, T6) -> R,
+): Flow<R> {
+ return combine(flow, flow2, flow3, flow4, flow5, flow6, transform)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 079c72f049a6..1f92bc1df9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,11 +37,8 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IVolumeController;
-import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
-import android.media.RoutingSessionInfo;
import android.media.VolumePolicy;
-import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.Token;
import android.net.Uri;
@@ -88,7 +85,6 @@ import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -217,7 +213,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
VolumeDialogControllerImpl.class.getSimpleName());
mWorker = new W(mWorkerLooper);
mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
- mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
+ mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
mAudioSharingInteractor = audioSharingInteractor;
mJavaAdapter = javaAdapter;
@@ -1360,16 +1356,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
- private final boolean mVolumeAdjustmentForRemoteGroupSessions;
-
- public MediaSessionsCallbacks(Context context) {
- mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
- }
@Override
public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
- if (showForSession(token)) {
addStream(token, "onRemoteUpdate");
int stream = 0;
@@ -1396,12 +1385,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + " of " + ss.levelMax);
mCallbacks.onStateChanged(mState);
}
- }
}
@Override
public void onRemoteVolumeChanged(Token token, int flags) {
- if (showForSession(token)) {
addStream(token, "onRemoteVolumeChanged");
int stream = 0;
synchronized (mRemoteStreams) {
@@ -1420,27 +1407,27 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
if (showUI) {
onShowRequestedW(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
}
- }
}
@Override
public void onRemoteRemoved(Token token) {
- if (showForSession(token)) {
- int stream = 0;
- synchronized (mRemoteStreams) {
- if (!mRemoteStreams.containsKey(token)) {
- Log.d(TAG, "onRemoteRemoved: stream doesn't exist, "
- + "aborting remote removed for token:" + token.toString());
- return;
- }
- stream = mRemoteStreams.get(token);
- }
- mState.states.remove(stream);
- if (mState.activeStream == stream) {
- updateActiveStreamW(-1);
+ int stream;
+ synchronized (mRemoteStreams) {
+ if (!mRemoteStreams.containsKey(token)) {
+ Log.d(
+ TAG,
+ "onRemoteRemoved: stream doesn't exist, "
+ + "aborting remote removed for token:"
+ + token.toString());
+ return;
}
- mCallbacks.onStateChanged(mState);
+ stream = mRemoteStreams.get(token);
+ }
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
}
+ mCallbacks.onStateChanged(mState);
}
public void setStreamVolume(int stream, int level) {
@@ -1449,39 +1436,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
}
- if (showForSession(token)) {
- mMediaSessions.setVolume(token, level);
- }
- }
-
- private boolean showForSession(Token token) {
- if (mVolumeAdjustmentForRemoteGroupSessions) {
- if (DEBUG) {
- Log.d(TAG, "Volume adjustment for remote group sessions allowed,"
- + " showForSession: true");
- }
- return true;
- }
- MediaController ctr = new MediaController(mContext, token);
- String packageName = ctr.getPackageName();
- List<RoutingSessionInfo> sessions =
- mRouter2Manager.getRoutingSessions(packageName);
- if (DEBUG) {
- Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name "
- + packageName);
- }
- for (RoutingSessionInfo session : sessions) {
- if (DEBUG) {
- Log.d(TAG, "Found routingSessionInfo: " + session);
- }
- if (!session.isSystemSession()
- && session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
- return true;
- }
- }
-
- Log.d(TAG, "No routing session for " + packageName);
- return false;
+ mMediaSessions.setVolume(token, level);
}
private Token findToken(int stream) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index ebb9ce9909bd..ed8de69ec482 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -23,6 +23,7 @@ import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
+import com.android.systemui.Flags;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
@@ -40,6 +41,8 @@ import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelDialogReceiver;
import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.dialog.VolumeDialogPlugin;
+import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent;
import com.android.systemui.volume.domain.interactor.VolumeDialogInteractor;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
@@ -66,7 +69,8 @@ import dagger.multibindings.IntoSet;
SpatializerModule.class,
},
subcomponents = {
- VolumePanelComponent.class
+ VolumePanelComponent.class,
+ VolumeDialogPluginComponent.class,
}
)
public interface VolumeModule {
@@ -101,6 +105,7 @@ public interface VolumeModule {
/** */
@Provides
static VolumeDialog provideVolumeDialog(
+ Lazy<VolumeDialogPlugin> volumeDialogProvider,
Context context,
VolumeDialogController volumeDialogController,
AccessibilityManagerWrapper accessibilityManagerWrapper,
@@ -118,29 +123,33 @@ public interface VolumeModule {
VibratorHelper vibratorHelper,
SystemClock systemClock,
VolumeDialogInteractor interactor) {
- VolumeDialogImpl impl = new VolumeDialogImpl(
- context,
- volumeDialogController,
- accessibilityManagerWrapper,
- deviceProvisionedController,
- configurationController,
- mediaOutputDialogManager,
- interactionJankMonitor,
- volumePanelNavigationInteractor,
- volumeNavigator,
- true, /* should listen for jank */
- csdFactory,
- devicePostureController,
- Looper.getMainLooper(),
- volumePanelFlag,
- dumpManager,
- secureSettings,
- vibratorHelper,
- systemClock,
- interactor);
- impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
- impl.setAutomute(true);
- impl.setSilentMode(false);
- return impl;
+ if (Flags.volumeRedesign()) {
+ return volumeDialogProvider.get();
+ } else {
+ VolumeDialogImpl impl = new VolumeDialogImpl(
+ context,
+ volumeDialogController,
+ accessibilityManagerWrapper,
+ deviceProvisionedController,
+ configurationController,
+ mediaOutputDialogManager,
+ interactionJankMonitor,
+ volumePanelNavigationInteractor,
+ volumeNavigator,
+ true, /* should listen for jank */
+ csdFactory,
+ devicePostureController,
+ Looper.getMainLooper(),
+ volumePanelFlag,
+ dumpManager,
+ secureSettings,
+ vibratorHelper,
+ systemClock,
+ interactor);
+ impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ impl.setAutomute(true);
+ impl.setSilentMode(false);
+ return impl;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
new file mode 100644
index 000000000000..7476c6a279f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.ContextThemeWrapper
+import android.view.MotionEvent
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogBinder
+import javax.inject.Inject
+
+class VolumeDialog
+@Inject
+constructor(
+ @Application context: Context,
+ private val dialogBinder: VolumeDialogBinder,
+ private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ dialogBinder.bind(this)
+ }
+
+ /**
+ * NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside of
+ * the touchable region of the volume dialog (as returned by [.onComputeInternalInsets]) even if
+ * those touches occurred within the bounds of the volume dialog.
+ */
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isShowing) {
+ if (event.action == MotionEvent.ACTION_OUTSIDE) {
+ visibilityInteractor.dismissDialog(Events.DISMISS_REASON_TOUCH_OUTSIDE)
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
new file mode 100644
index 000000000000..4b7a9782cc6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class VolumeDialogPlugin
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
+) : VolumeDialog {
+
+ private var job: Job? = null
+
+ override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
+ job =
+ applicationCoroutineScope.launch {
+ coroutineScope {
+ val component = volumeDialogPluginComponentFactory.create(this)
+
+ component.viewModel().activate()
+ }
+ }
+ }
+
+ override fun destroy() {
+ job?.cancel()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
new file mode 100644
index 000000000000..f7ad3205f3dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger
+
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Core Volume Dialog dagger component. It's managed by
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
+ */
+@VolumeDialogScope
+@Subcomponent(modules = [])
+interface VolumeDialogComponent {
+
+ /**
+ * Provides a coroutine scope to use inside [VolumeDialogScope].
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
+ * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
+ * dialog is not shown.
+ */
+ @VolumeDialog fun coroutineScope(): CoroutineScope
+
+ @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
new file mode 100644
index 000000000000..4e0098ccdf99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogPluginComponent.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger
+
+import com.android.systemui.volume.dialog.dagger.module.VolumeDialogPluginModule
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Volume Dialog plugin dagger component. It's managed by
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
+ */
+@VolumeDialogPluginScope
+@Subcomponent(modules = [VolumeDialogPluginModule::class])
+interface VolumeDialogPluginComponent {
+
+ fun viewModel(): VolumeDialogPluginViewModel
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(
+ @BindsInstance @VolumeDialogPlugin scope: CoroutineScope
+ ): VolumeDialogPluginComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
new file mode 100644
index 000000000000..3fdf86a923fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.module
+
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import dagger.Module
+
+@Module(subcomponents = [VolumeDialogComponent::class]) interface VolumeDialogPluginModule
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt
new file mode 100644
index 000000000000..34bddb42b891
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialog.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.scope
+
+import javax.inject.Qualifier
+
+/**
+ * Volume Dialog qualifier.
+ *
+ * @see VolumeDialogScope
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeDialog
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt
new file mode 100644
index 000000000000..1038c30c1e9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPlugin.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.scope
+
+import javax.inject.Qualifier
+
+/**
+ * Volume Dialog plugin qualifier.
+ *
+ * @see VolumeDialogPluginScope
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class VolumeDialogPlugin
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt
new file mode 100644
index 000000000000..6c5f672ba2c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogPluginScope.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.scope
+
+import javax.inject.Scope
+
+/**
+ * Volume Dialog plugin dependency injection scope. This scope is created alongside Volume Dialog
+ * plugin is initialized and destroyed alongside it. This is effectively almost similar
+ * to @Application now.
+ */
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class VolumeDialogPluginScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt
new file mode 100644
index 000000000000..52caa6a42ab4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/scope/VolumeDialogScope.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.scope
+
+import javax.inject.Scope
+
+/**
+ * Volume Panel dependency injection scope. This scope is created alongside Volume Panel and
+ * destroyed when it's lo longer present.
+ */
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class VolumeDialogScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
new file mode 100644
index 000000000000..2e26fd6de410
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.annotation.SuppressLint
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.shareIn
+
+private const val BUFFER_CAPACITY = 16
+
+/**
+ * Exposes [VolumeDialogController] callback events in the [event].
+ *
+ * @see VolumeDialogController.Callbacks
+ */
+@VolumeDialogPluginScope
+class VolumeDialogCallbacksInteractor
+@Inject
+constructor(
+ private val volumeDialogController: VolumeDialogController,
+ @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
+ @Background private val bgHandler: Handler,
+) {
+
+ @SuppressLint("SharedFlowCreation") // event-but needed
+ val event: Flow<VolumeDialogEventModel> =
+ callbackFlow {
+ val producer = VolumeDialogEventModelProducer(this)
+ volumeDialogController.addCallback(producer, bgHandler)
+ awaitClose { volumeDialogController.removeCallback(producer) }
+ }
+ .buffer(BUFFER_CAPACITY)
+ .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed())
+
+ private class VolumeDialogEventModelProducer(
+ private val scope: ProducerScope<VolumeDialogEventModel>
+ ) : VolumeDialogController.Callbacks {
+ override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
+ scope.trySend(
+ VolumeDialogEventModel.ShowRequested(
+ reason = reason,
+ keyguardLocked = keyguardLocked,
+ lockTaskModeState = lockTaskModeState,
+ )
+ )
+ }
+
+ override fun onDismissRequested(reason: Int) {
+ scope.trySend(VolumeDialogEventModel.DismissRequested(reason))
+ }
+
+ override fun onStateChanged(state: VolumeDialogController.State?) {
+ if (state != null) {
+ scope.trySend(VolumeDialogEventModel.StateChanged(VolumeDialogStateModel(state)))
+ }
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int) {
+ scope.trySend(VolumeDialogEventModel.LayoutDirectionChanged(layoutDirection))
+ }
+
+ // Configuration change is never emitted by the VolumeDialogControllerImpl now.
+ override fun onConfigurationChanged() = Unit
+
+ override fun onShowVibrateHint() {
+ scope.trySend(VolumeDialogEventModel.ShowVibrateHint)
+ }
+
+ override fun onShowSilentHint() {
+ scope.trySend(VolumeDialogEventModel.ShowSilentHint)
+ }
+
+ override fun onScreenOff() {
+ scope.trySend(VolumeDialogEventModel.ScreenOff)
+ }
+
+ override fun onShowSafetyWarning(flags: Int) {
+ scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags))
+ }
+
+ override fun onAccessibilityModeChanged(showA11yStream: Boolean) {
+ scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream))
+ }
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionComponentStateChanged(
+ isComponentEnabled: Boolean,
+ fromTooltip: Boolean,
+ ) = Unit
+
+ // Captions button is remove from the Volume Dialog
+ override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) =
+ Unit
+
+ override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) {
+ scope.trySend(
+ VolumeDialogEventModel.ShowCsdWarning(
+ csdWarning = csdWarning,
+ durationMs = durationMs,
+ )
+ )
+ }
+
+ override fun onVolumeChangedFromKey() {
+ scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
new file mode 100644
index 000000000000..4a709a44b42f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Exposes [VolumeDialogController.getState] in the [volumeDialogState].
+ *
+ * @see [VolumeDialogController]
+ */
+@VolumeDialogPluginScope
+class VolumeDialogStateInteractor
+@Inject
+constructor(
+ volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor,
+ private val volumeDialogController: VolumeDialogController,
+ @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
+) {
+
+ val volumeDialogState: Flow<VolumeDialogStateModel> =
+ volumeDialogCallbacksInteractor.event
+ .onStart { volumeDialogController.getState() }
+ .filterIsInstance(VolumeDialogEventModel.StateChanged::class)
+ .map { it.state }
+ .stateIn(scope = coroutineScope, started = SharingStarted.Eagerly, initialValue = null)
+ .filterNotNull()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
new file mode 100644
index 000000000000..6c92754b6f60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.annotation.SuppressLint
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.update
+
+private val maxDialogShowTime: Duration = 3.seconds
+
+/**
+ * Handles Volume Dialog visibility state. It might change from several sources:
+ * - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
+ * - it might be dismissed by the inactivity timeout;
+ * - it can be dismissed by the user;
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogVisibilityInteractor
+@Inject
+constructor(
+ @VolumeDialogPlugin coroutineScope: CoroutineScope,
+ callbacksInteractor: VolumeDialogCallbacksInteractor,
+) {
+
+ @SuppressLint("SharedFlowCreation")
+ private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+ private val mutableDialogVisibility =
+ MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible)
+
+ val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow()
+
+ init {
+ merge(
+ mutableDismissDialogEvents.mapLatest {
+ delay(maxDialogShowTime)
+ VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
+ },
+ callbacksInteractor.event,
+ )
+ .onEach { event ->
+ VolumeDialogVisibilityModel.fromEvent(event)?.let { model ->
+ mutableDialogVisibility.value = model
+ if (model is VolumeDialogVisibilityModel.Visible) {
+ resetDismissTimeout()
+ }
+ }
+ }
+ .launchIn(coroutineScope)
+ }
+
+ /**
+ * Dismisses the dialog with a given [reason]. The new state will be emitted in the
+ * [dialogVisibility].
+ */
+ fun dismissDialog(reason: Int) {
+ mutableDialogVisibility.update {
+ if (it is VolumeDialogVisibilityModel.Dismissed) {
+ it
+ } else {
+ VolumeDialogVisibilityModel.Dismissed(reason)
+ }
+ }
+ }
+
+ /** Resets current dialog timeout. */
+ suspend fun resetDismissTimeout() {
+ mutableDismissDialogEvents.emit(Unit)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
new file mode 100644
index 000000000000..ca0310ef8588
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.media.AudioManager
+
+/**
+ * Models VolumeDialogController callback events.
+ *
+ * @see VolumeDialogController.Callbacks
+ */
+sealed interface VolumeDialogEventModel {
+
+ data class ShowRequested(
+ val reason: Int,
+ val keyguardLocked: Boolean,
+ val lockTaskModeState: Int,
+ ) : VolumeDialogEventModel
+
+ data class DismissRequested(val reason: Int) : VolumeDialogEventModel
+
+ data class StateChanged(val state: VolumeDialogStateModel) : VolumeDialogEventModel
+
+ data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel
+
+ data object ShowVibrateHint : VolumeDialogEventModel
+
+ data object ShowSilentHint : VolumeDialogEventModel
+
+ data object ScreenOff : VolumeDialogEventModel
+
+ data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel
+
+ data class AccessibilityModeChanged(val showA11yStream: Boolean) : VolumeDialogEventModel
+
+ data class ShowCsdWarning(@AudioManager.CsdWarning val csdWarning: Int, val durationMs: Int) :
+ VolumeDialogEventModel
+
+ data object VolumeChangedFromKey : VolumeDialogEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
new file mode 100644
index 000000000000..f1443e36d019
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.content.ComponentName
+import android.util.SparseArray
+import androidx.core.util.keyIterator
+import com.android.systemui.plugins.VolumeDialogController
+
+/** Models a state of the Volume Dialog. */
+data class VolumeDialogStateModel(
+ val states: Map<Int, VolumeDialogStreamStateModel>,
+ val ringerModeInternal: Int = 0,
+ val ringerModeExternal: Int = 0,
+ val zenMode: Int = 0,
+ val effectsSuppressor: ComponentName? = null,
+ val effectsSuppressorName: String? = null,
+ val activeStream: Int = NO_ACTIVE_STREAM,
+ val disallowAlarms: Boolean = false,
+ val disallowMedia: Boolean = false,
+ val disallowSystem: Boolean = false,
+ val disallowRinger: Boolean = false,
+) {
+
+ constructor(
+ legacyState: VolumeDialogController.State
+ ) : this(
+ states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) },
+ ringerModeInternal = legacyState.ringerModeInternal,
+ ringerModeExternal = legacyState.ringerModeExternal,
+ zenMode = legacyState.zenMode,
+ effectsSuppressor = legacyState.effectsSuppressor,
+ effectsSuppressorName = legacyState.effectsSuppressorName,
+ activeStream = legacyState.activeStream,
+ disallowAlarms = legacyState.disallowAlarms,
+ disallowMedia = legacyState.disallowMedia,
+ disallowSystem = legacyState.disallowSystem,
+ disallowRinger = legacyState.disallowRinger,
+ )
+
+ companion object {
+ const val NO_ACTIVE_STREAM: Int = -1
+ }
+}
+
+private fun <INPUT, OUTPUT> SparseArray<INPUT>.mapToMap(map: (INPUT) -> OUTPUT): Map<Int, OUTPUT> {
+ val resultMap = mutableMapOf<Int, OUTPUT>()
+ for (key in keyIterator()) {
+ val mappedValue: OUTPUT = map(get(key)!!)
+ resultMap[key] = mappedValue
+ }
+ return resultMap
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
new file mode 100644
index 000000000000..a9d367da41e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+import android.annotation.IntegerRes
+import com.android.systemui.plugins.VolumeDialogController
+
+/** Models a state of an audio stream of the Volume Dialog. */
+data class VolumeDialogStreamStateModel(
+ val isDynamic: Boolean = false,
+ val level: Int = 0,
+ val levelMin: Int = 0,
+ val levelMax: Int = 0,
+ val muted: Boolean = false,
+ val muteSupported: Boolean = false,
+ @IntegerRes val name: Int = 0,
+ val remoteLabel: String? = null,
+ val routedToBluetooth: Boolean = false,
+) {
+ constructor(
+ legacyState: VolumeDialogController.StreamState
+ ) : this(
+ isDynamic = legacyState.dynamic,
+ level = legacyState.level,
+ levelMin = legacyState.levelMin,
+ levelMax = legacyState.levelMax,
+ muted = legacyState.muted,
+ muteSupported = legacyState.muteSupported,
+ name = legacyState.name,
+ remoteLabel = legacyState.remoteLabel,
+ routedToBluetooth = legacyState.routedToBluetooth,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
new file mode 100644
index 000000000000..646445d33f51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.model
+
+/** Models current Volume Dialog visibility state. */
+sealed interface VolumeDialogVisibilityModel {
+
+ /** Dialog is currently visible. */
+ data class Visible(val reason: Int, val keyguardLocked: Boolean, val lockTaskModeState: Int) :
+ VolumeDialogVisibilityModel
+
+ /** Dialog has never been shown. So it's just invisible. */
+ interface Invisible : VolumeDialogVisibilityModel {
+ companion object : Invisible
+ }
+
+ /** Dialog has been shown and then dismissed. */
+ data class Dismissed(val reason: Int) : Invisible
+
+ companion object {
+
+ /**
+ * Creates [VolumeDialogVisibilityModel] from appropriate events and returns null otherwise.
+ */
+ fun fromEvent(event: VolumeDialogEventModel): VolumeDialogVisibilityModel? {
+ return when (event) {
+ is VolumeDialogEventModel.DismissRequested -> Dismissed(event.reason)
+ is VolumeDialogEventModel.ShowRequested ->
+ Visible(event.reason, event.keyguardLocked, event.lockTaskModeState)
+ else -> null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
new file mode 100644
index 000000000000..59c38c019823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.volume.dialog.shared
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import com.android.systemui.volume.Events
+import javax.inject.Inject
+
+private const val TAG = "SysUI_VolumeDialog"
+
+/** Logs events related to the Volume Panel. */
+class VolumeDialogLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
+
+ fun onShow(reason: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = reason },
+ { "Show: ${Events.SHOW_REASONS[int1]}" },
+ )
+ }
+
+ fun onDismiss(reason: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = reason },
+ { "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
new file mode 100644
index 000000000000..3f2c39bba6e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.app.Dialog
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+@VolumeDialogScope
+class VolumeDialogBinder
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val volumeDialogViewBinder: VolumeDialogViewBinder,
+ private val gravityViewModel: VolumeDialogGravityViewModel,
+) {
+
+ fun bind(dialog: Dialog) {
+ with(dialog) {
+ setupWindow(window!!)
+ dialog.setContentView(R.layout.volume_dialog)
+
+ val volumeDialogView: View = dialog.requireViewById(R.id.volume_dialog_container)
+ volumeDialogView.repeatWhenAttached {
+ lifecycleScope.launch { volumeDialogViewBinder.bind(volumeDialogView) }
+ }
+ }
+ }
+
+ /** Configures [Window] for the [Dialog]. */
+ private fun setupWindow(window: Window) =
+ with(window) {
+ clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ addFlags(
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ )
+ addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+ requestFeature(Window.FEATURE_NO_TITLE)
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ setWindowAnimations(-1)
+ setFormat(PixelFormat.TRANSLUCENT)
+
+ attributes =
+ attributes.apply {
+ title = "VolumeDialog" // Not the same as Window#setTitle
+ }
+ setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+ gravityViewModel.dialogGravity.onEach { window.setGravity(it) }.launchIn(coroutineScope)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
new file mode 100644
index 000000000000..700225d521c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+class VolumeDialogViewBinder
+@Inject
+constructor(private val volumeDialogViewModelFactory: VolumeDialogViewModel.Factory) {
+
+ suspend fun bind(view: View) {
+ view.repeatWhenAttached {
+ view.viewModel(
+ traceName = "VolumeDialogViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { volumeDialogViewModelFactory.create() },
+ ) { viewModel ->
+ view.setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
new file mode 100644
index 000000000000..df6523c9d750
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.Gravity
+import androidx.annotation.GravityInt
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@VolumeDialogScope
+class VolumeDialogGravityViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
+ configurationController: ConfigurationController,
+ private val devicePostureController: DevicePostureController,
+) {
+
+ @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
+
+ val dialogGravity: Flow<Int> =
+ combine(
+ devicePostureController.devicePosture(),
+ configurationController.onConfigChanged.onEach { onConfigurationChanged() },
+ ) { devicePosture, configuration ->
+ context.calculateGravity(devicePosture, configuration)
+ }
+ .stateIn(
+ scope = coroutineScope,
+ started = SharingStarted.Eagerly,
+ context.calculateGravity(),
+ )
+
+ private suspend fun onConfigurationChanged() {
+ withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
+ }
+
+ @GravityInt
+ private fun Context.calculateGravity(
+ devicePosture: Int = devicePostureController.devicePosture,
+ config: Configuration = resources.configuration,
+ ): Int {
+ val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+ val gravity =
+ if (isLandscape && isHalfOpen) {
+ originalGravity or Gravity.TOP
+ } else {
+ originalGravity
+ }
+ return getAbsoluteGravity(gravity)
+ }
+}
+
+@GravityInt
+private fun Context.getAbsoluteGravity(
+ gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
+): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
new file mode 100644
index 000000000000..329a947f24ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import android.app.Dialog
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogPluginScope
+class VolumeDialogPluginViewModel
+@Inject
+constructor(
+ private val componentFactory: VolumeDialogComponent.Factory,
+ private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+ private val controller: VolumeDialogController,
+ private val logger: VolumeDialogLogger,
+) : ExclusiveActivatable() {
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ dialogVisibilityInteractor.dialogVisibility
+ .mapLatest { visibilityModel ->
+ with(visibilityModel) {
+ if (this is VolumeDialogVisibilityModel.Visible) {
+ showDialog(reason, keyguardLocked, lockTaskModeState)
+ }
+ if (this is VolumeDialogVisibilityModel.Dismissed) {
+ Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+ logger.onDismiss(reason)
+ }
+ }
+ }
+ .launchIn(this)
+ }
+ awaitCancellation()
+ }
+
+ suspend fun showDialog(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int): Unit =
+ coroutineScope {
+ logger.onShow(reason)
+
+ controller.notifyVisible(true)
+
+ val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this)
+ val dialog =
+ volumeDialogComponent.volumeDialog().apply {
+ setOnDismissListener {
+ volumeDialogComponent.coroutineScope().cancel()
+ dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
+ }
+ }
+ launch { dialog.awaitShow() }
+
+ Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+ }
+}
+
+/** Shows [Dialog] until suspend function is cancelled. */
+private suspend fun Dialog.awaitShow() =
+ suspendCancellableCoroutine<Unit> {
+ show()
+ it.invokeOnCancellation { dismiss() }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 4cee874f5f8e..30c8c15387eb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -14,22 +14,21 @@
* limitations under the License.
*/
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.volume.dialog.ui.viewmodel
-import android.graphics.Region
-import com.android.systemui.scene.data.repository.SystemGestureExclusionRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
-class SystemGestureExclusionInteractor
-@Inject
-constructor(private val repository: SystemGestureExclusionRepository) {
+class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() {
- /**
- * Returns [Flow] of the [Region] in which system gestures should be excluded on the display
- * identified with [displayId].
- */
- fun exclusionRegion(displayId: Int): Flow<Region?> {
- return repository.exclusionRegion(displayId)
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): VolumeDialogViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5f6ad9205ec7..02d0b577feb1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -61,7 +61,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -393,7 +393,7 @@ public final class WMShell implements
void initDesktopMode(DesktopMode desktopMode) {
desktopMode.addVisibleTasksListener(
- new DesktopModeTaskRepository.VisibleTasksListener() {
+ new DesktopRepository.VisibleTasksListener() {
@Override
public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e609d5f93626..34ebba844926 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -127,6 +127,7 @@ import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
+import com.android.keyguard.logging.SimLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -267,6 +268,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
@Mock
+ private SimLogger mSimLogger;
+ @Mock
private SessionTracker mSessionTracker;
@Mock
private UiEventLogger mUiEventLogger;
@@ -2234,6 +2237,36 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testOnSimStateChanged_LockedToNotReadyToLocked() {
+ int validSubId = 10;
+ int slotId = 0;
+
+ KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy(
+ KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
+ // Initially locked
+ mKeyguardUpdateMonitor.handleSimStateChange(validSubId, slotId,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ verify(keyguardUpdateMonitorCallback).onSimStateChanged(validSubId, slotId,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ reset(keyguardUpdateMonitorCallback);
+ // Not ready, with invalid sub id
+ mKeyguardUpdateMonitor.handleSimStateChange(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ slotId, TelephonyManager.SIM_STATE_NOT_READY);
+ verify(keyguardUpdateMonitorCallback).onSimStateChanged(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, slotId,
+ TelephonyManager.SIM_STATE_NOT_READY);
+
+ reset(keyguardUpdateMonitorCallback);
+ // Back to PIN required, which notifies listeners
+ mKeyguardUpdateMonitor.handleSimStateChange(validSubId, slotId,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ verify(keyguardUpdateMonitorCallback).onSimStateChanged(validSubId, slotId,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ }
+
+ @Test
public void onAuthEnrollmentChangesCallbacksAreNotified() {
KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass(
@@ -2487,7 +2520,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
- mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
+ mKeyguardUpdateMonitorLogger, mSimLogger, mUiEventLogger, () -> mSessionTracker,
mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFingerprintManager, mBiometricManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 5fc19711a26c..6061063db903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.clipboardoverlay;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -104,6 +105,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Mock
private Animator mAnimator;
+ @Mock
+ private Animator mEndAnimator;
private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor =
ArgumentCaptor.forClass(Animator.AnimatorListener.class);
@@ -123,7 +126,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
- when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayView.getExitAnimation()).thenReturn(mEndAnimator);
when(mClipboardOverlayView.getFadeOutAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 0)));
@@ -164,6 +167,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
public void test_setClipData_invalidImageData_legacy() {
initController();
@@ -236,6 +240,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
public void test_setClipData_invalidImageData() {
initController();
@@ -318,11 +323,11 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mOverlayController.setClipData(mSampleClipData, "");
mCallbacks.onShareButtonTapped();
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
- mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
- verify(mClipboardOverlayView, times(1)).getFadeOutAnimation();
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@Test
@@ -343,8 +348,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
initController();
mCallbacks.onDismissButtonTapped();
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
- mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
// package name is null since we haven't actually set a source for this test
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, null);
@@ -403,14 +408,18 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mOverlayController.setClipData(mSampleClipData, "first.package");
mCallbacks.onShareButtonTapped();
+ verify(mEndAnimator).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
mOverlayController.setClipData(mSampleClipData, "second.package");
mCallbacks.onShareButtonTapped();
+ verify(mEndAnimator, times(2)).addListener(mAnimatorListenerCaptor.capture());
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(mEndAnimator);
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "second.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
verifyNoMoreInteractions(mUiEventLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index ad2550255e29..7d5a334b45ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -148,6 +148,31 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
)
}
+ @Test
+ fun migrate3To4_addSpanYColumn_defaultValuePopulated() {
+ val databaseV3 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 3)
+
+ val fakeWidgetsV3 =
+ listOf(
+ FakeCommunalWidgetItemV3(1, "test_widget_1", 11, 0),
+ FakeCommunalWidgetItemV3(2, "test_widget_2", 12, 10),
+ FakeCommunalWidgetItemV3(3, "test_widget_3", 13, 0),
+ )
+ databaseV3.insertWidgetsV3(fakeWidgetsV3)
+
+ databaseV3.verifyWidgetsV3(fakeWidgetsV3)
+
+ val databaseV4 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 4,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_3_4,
+ )
+
+ databaseV4.verifyWidgetsV4(fakeWidgetsV3.map { it.getV4() })
+ }
+
private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
widgets.forEach { widget ->
execSQL(
@@ -157,6 +182,22 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
}
}
+ private fun SupportSQLiteDatabase.insertWidgetsV3(widgets: List<FakeCommunalWidgetItemV3>) {
+ widgets.forEach { widget ->
+ execSQL(
+ "INSERT INTO communal_widget_table(" +
+ "widget_id, " +
+ "component_name, " +
+ "item_id, " +
+ "user_serial_number) " +
+ "VALUES(${widget.widgetId}, " +
+ "'${widget.componentName}', " +
+ "${widget.itemId}, " +
+ "${widget.userSerialNumber})"
+ )
+ }
+ }
+
private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
val cursor = query("SELECT * FROM communal_widget_table")
assertThat(cursor.moveToFirst()).isTrue()
@@ -193,6 +234,42 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
assertThat(cursor.isAfterLast).isTrue()
}
+ private fun SupportSQLiteDatabase.verifyWidgetsV3(widgets: List<FakeCommunalWidgetItemV3>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+
+ cursor.moveToNext()
+ }
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV4(widgets: List<FakeCommunalWidgetItemV4>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+ assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY)
+
+ cursor.moveToNext()
+ }
+
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
ranks.forEach { rank ->
execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
@@ -238,10 +315,27 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
val userSerialNumber: Int,
)
- private data class FakeCommunalItemRank(
- val rank: Int,
+ private fun FakeCommunalWidgetItemV3.getV4(): FakeCommunalWidgetItemV4 {
+ return FakeCommunalWidgetItemV4(widgetId, componentName, itemId, userSerialNumber, 3)
+ }
+
+ private data class FakeCommunalWidgetItemV3(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ )
+
+ private data class FakeCommunalWidgetItemV4(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ val spanY: Int,
)
+ private data class FakeCommunalItemRank(val rank: Int)
+
companion object {
private const val DATABASE_NAME = "communal_db"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2f8f45cb0197..0b9c06f2dbe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -901,7 +901,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
// 1st time is onStart(), 2nd time is getActiveAutoSwitchNonDdsSubId()
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
// Adds non DDS subId again
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -912,7 +912,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
// Does not add due to cached subInfo in mSubIdTelephonyCallbackMap.
verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
- assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size()).isEqualTo(2);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 148a2e56f2a5..52266eee24e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -29,7 +29,6 @@ import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.kotlin.any
@@ -47,16 +46,11 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
private val uiEventLogger = mock<UiEventLogger>()
private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>()
- private val request = ScreenshotData.forTesting()
+ private val request = ScreenshotData.forTesting(userHandle = UserHandle.OWNER)
private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
private lateinit var actionsProvider: ScreenshotActionsProvider
- @Before
- fun setUp() {
- request.userHandle = UserHandle.OWNER
- }
-
@Test
fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
actionsProvider = createActionsProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index 15da77dfe33d..4000d6cde2fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -47,7 +47,7 @@ class MessageContainerControllerTest : SysuiTestCase() {
lateinit var screenshotView: ViewGroup
val userHandle = UserHandle.of(5)
- val screenshotData = ScreenshotData.forTesting()
+ val screenshotData = ScreenshotData.forTesting(userHandle = userHandle)
val appName = "app name"
lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
@@ -61,7 +61,7 @@ class MessageContainerControllerTest : SysuiTestCase() {
workProfileMessageController,
profileMessageController,
screenshotDetectionController,
- TestScope(UnconfinedTestDispatcher())
+ TestScope(UnconfinedTestDispatcher()),
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -83,8 +83,6 @@ class MessageContainerControllerTest : SysuiTestCase() {
container.addView(detectionNoticeView)
messageContainer.setView(screenshotView)
-
- screenshotData.userHandle = userHandle
}
@Test
@@ -92,7 +90,7 @@ class MessageContainerControllerTest : SysuiTestCase() {
val profileData =
ProfileMessageController.ProfileFirstRunData(
LabeledIcon(appName, icon),
- ProfileMessageController.FirstRunProfile.PRIVATE
+ ProfileMessageController.FirstRunProfile.PRIVATE,
)
whenever(profileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(profileData)
messageContainer.onScreenshotTaken(screenshotData)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
index 3ed09770b189..1d74e8b3002c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -17,12 +17,12 @@
package com.android.systemui.screenshot
import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.util.ScreenshotRequest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -71,15 +71,6 @@ class ScreenshotDataTest {
}
@Test
- fun testNegativeUserId() {
- val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
-
- val data = ScreenshotData.fromRequest(request)
-
- assertThat(data.userHandle).isNull()
- }
-
- @Test
fun testPackageNameAsString() {
val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index 1538c72f8df8..c50702868025 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -61,8 +61,8 @@ class ScreenshotDetectionControllerTest {
@Test
fun testMaybeNotifyOfScreenshot_ignoresOverview() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW)
val list = controller.maybeNotifyOfScreenshot(data)
@@ -72,8 +72,8 @@ class ScreenshotDetectionControllerTest {
@Test
fun testMaybeNotifyOfScreenshot_emptySet() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
.thenReturn(listOf())
@@ -85,8 +85,8 @@ class ScreenshotDetectionControllerTest {
@Test
fun testMaybeNotifyOfScreenshot_oneApp() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component = ComponentName("package1", "class1")
val appName = "app name"
@@ -95,7 +95,7 @@ class ScreenshotDetectionControllerTest {
whenever(
packageManager.getActivityInfo(
eq(component),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo)
@@ -112,8 +112,8 @@ class ScreenshotDetectionControllerTest {
@Test
fun testMaybeNotifyOfScreenshot_multipleApps() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component1 = ComponentName("package1", "class1")
val component2 = ComponentName("package2", "class2")
@@ -129,21 +129,21 @@ class ScreenshotDetectionControllerTest {
whenever(
packageManager.getActivityInfo(
eq(component1),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo1)
whenever(
packageManager.getActivityInfo(
eq(component2),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo2)
whenever(
packageManager.getActivityInfo(
eq(component3),
- any(PackageManager.ComponentInfoFlags::class.java)
+ any(PackageManager.ComponentInfoFlags::class.java),
)
)
.thenReturn(activityInfo3)
@@ -165,11 +165,13 @@ class ScreenshotDetectionControllerTest {
private fun includesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
ComponentInfoFlagMatcher(mask, mask)
+
private fun excludesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
ComponentInfoFlagMatcher(mask, 0)
private class ComponentInfoFlagMatcher(
- @PackageManager.ComponentInfoFlagsBits val mask: Int, val value: Int
+ @PackageManager.ComponentInfoFlagsBits val mask: Int,
+ val value: Int,
) : ArgumentMatcher<PackageManager.ComponentInfoFlags> {
override fun matches(flags: PackageManager.ComponentInfoFlags?): Boolean {
return flags != null && (mask.toLong() and flags.value) == value.toLong()
@@ -182,26 +184,28 @@ class ScreenshotDetectionControllerTest {
@Test
fun testMaybeNotifyOfScreenshot_disabledApp() {
- val data = ScreenshotData.forTesting()
- data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+ val data =
+ ScreenshotData.forTesting(source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD)
val component = ComponentName("package1", "class1")
val appName = "app name"
val activityInfo = mock(ActivityInfo::class.java)
whenever(
- packageManager.getActivityInfo(
- eq(component),
- argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS or MATCH_ANY_USER))
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS or MATCH_ANY_USER)),
+ )
)
- ).thenReturn(activityInfo)
+ .thenReturn(activityInfo)
whenever(
- packageManager.getActivityInfo(
- eq(component),
- argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS))
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS)),
+ )
)
- ).thenThrow(PackageManager.NameNotFoundException::class.java)
+ .thenThrow(PackageManager.NameNotFoundException::class.java)
whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
.thenReturn(listOf(component))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 15705fbfec33..27e9f07af168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -20,6 +20,7 @@ import com.android.internal.util.ScreenshotRequest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.FakeFocusedDisplayRepository
import com.android.systemui.display.data.repository.display
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -58,6 +59,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
private val testScope = TestScope(UnconfinedTestDispatcher())
private val eventLogger = UiEventLoggerFake()
private val headlessHandler = mock<HeadlessScreenshotHandler>()
+ private val focusedDisplayRepository = FakeFocusedDisplayRepository()
private val screenshotExecutor =
TakeScreenshotExecutorImpl(
@@ -68,6 +70,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
eventLogger,
notificationControllerFactory,
headlessHandler,
+ focusedDisplayRepository,
)
@Before
@@ -257,6 +260,111 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_fromOverview_honorsDisplay() =
+ testScope.runTest {
+ val displayId = 1
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ displayId = displayId,
+ source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW,
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(displayId)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_fromOverviewInvalidDisplay_usesDefault() =
+ testScope.runTest {
+ setDisplays(
+ display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
+ display(TYPE_EXTERNAL, id = 1),
+ )
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ displayId = 5,
+ source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW,
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_keyOther_usesFocusedDisplay() =
+ testScope.runTest {
+ val displayId = 1
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId))
+ val onSaved = { _: Uri? -> }
+ focusedDisplayRepository.emit(displayId)
+
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(displayId)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
+ fun executeScreenshots_keyOtherInvalidDisplay_usesDefault() =
+ testScope.runTest {
+ setDisplays(
+ display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY),
+ display(TYPE_EXTERNAL, id = 1),
+ )
+ focusedDisplayRepository.emit(5) // invalid display
+ val onSaved = { _: Uri? -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(
+ source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+ ),
+ onSaved,
+ callback,
+ )
+
+ val dataCaptor = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller).handleScreenshot(dataCaptor.capture(), any(), any())
+
+ assertThat(dataCaptor.value.displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -527,9 +635,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
runCurrent()
}
- private fun createScreenshotRequest(type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN) =
- ScreenshotRequest.Builder(type, WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER)
+ private fun createScreenshotRequest(
+ type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ source: Int = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER,
+ displayId: Int = Display.DEFAULT_DISPLAY,
+ ) =
+ ScreenshotRequest.Builder(type, source)
.setTopComponent(topComponent)
+ .setDisplayId(displayId)
.also {
if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
it.setBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index bab9bbbfde4f..2fcacb9880dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -58,13 +58,13 @@ class PolicyRequestProcessorTest {
ScreenshotData(
TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_KEY_CHORD,
- null,
+ UserHandle.CURRENT,
topComponent = null,
screenBounds = Rect(0, 0, 1, 1),
taskId = -1,
insets = Insets.NONE,
bitmap = null,
- displayId = DEFAULT_DISPLAY
+ displayId = DEFAULT_DISPLAY,
)
/* Create a policy request processor with no capture policies */
@@ -75,7 +75,7 @@ class PolicyRequestProcessorTest {
policies = emptyList(),
defaultOwner = UserHandle.of(PERSONAL),
defaultComponent = ComponentName("default", "Component"),
- displayTasks = fullScreenWork
+ displayTasks = fullScreenWork,
)
val result = runBlocking { requestProcessor.process(request) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
new file mode 100644
index 000000000000..040a9e959094
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.LogUtil
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SimpleDigitalClockTextViewTest : SysuiTestCase() {
+ private val messageBuffer = LogUtil.DEBUG_MESSAGE_BUFFER
+ private lateinit var underTest: SimpleDigitalClockTextView
+ private val defaultLargeClockTextSize = 500F
+ private val smallerTextSize = 300F
+ private val largerTextSize = 800F
+ private val firstMeasureTextSize = 100F
+
+ @Before
+ fun setup() {
+ underTest = SimpleDigitalClockTextView(context, messageBuffer)
+ underTest.textStyle = FontTextStyle()
+ underTest.aodStyle = FontTextStyle()
+ underTest.text = "0"
+ underTest.applyTextSize(defaultLargeClockTextSize)
+ }
+
+ @Test
+ fun applySmallerConstrainedTextSize_applyConstrainedTextSize() {
+ underTest.applyTextSize(smallerTextSize, constrainedByHeight = true)
+ assertEquals(smallerTextSize, underTest.textSize * underTest.fontSizeAdjustFactor)
+ }
+
+ @Test
+ fun applyLargerConstrainedTextSize_applyUnconstrainedTextSize() {
+ underTest.applyTextSize(largerTextSize, constrainedByHeight = true)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+
+ @Test
+ fun applyFirstMeasureConstrainedTextSize_getConstrainedTextSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(smallerTextSize, constrainedByHeight = true)
+ assertEquals(smallerTextSize, underTest.textSize * underTest.fontSizeAdjustFactor)
+ }
+
+ @Test
+ fun applySmallFirstMeasureConstrainedSizeAndLargerConstrainedTextSize_applyDefaultSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(largerTextSize, constrainedByHeight = true)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+
+ @Test
+ fun applyFirstMeasureConstrainedTextSize_applyUnconstrainedTextSize() {
+ underTest.applyTextSize(firstMeasureTextSize, constrainedByHeight = true)
+ underTest.applyTextSize(defaultLargeClockTextSize)
+ assertEquals(defaultLargeClockTextSize, underTest.textSize)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 98315d0ca3c9..83dbfa0682ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -95,6 +95,7 @@ import org.mockito.MockitoSession;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class NetworkControllerBaseTest extends SysuiTestCase {
@@ -332,10 +333,15 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
public void setConnectivityViaCallbackInNetworkControllerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
mDefaultCallbackInNetworkController.onCapabilitiesChanged(
mock(Network.class), builder.build());
@@ -385,10 +391,15 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
public void setConnectivityViaCallbackInWifiTrackerForVcn(
- int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
+ int networkType,
+ boolean validated,
+ boolean isConnected,
+ VcnTransportInfo info,
+ Network underlyingNetwork) {
final NetworkCapabilities.Builder builder =
new NetworkCapabilities.Builder(mNetCapabilities);
- builder.setTransportInfo(info);
+ builder.setTransportInfo(info)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
setConnectivityCommon(builder, networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
if (isConnected) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6c80a97625a7..6febb91db992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static junit.framework.Assert.assertEquals;
@@ -250,6 +251,17 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
}
+ private Network newWifiNetwork(WifiInfo wifiInfo) {
+ final Network network = mock(Network.class);
+ final NetworkCapabilities wifiCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(wifiInfo)
+ .build();
+ when(mMockCm.getNetworkCapabilities(network)).thenReturn(wifiCaps);
+ return network;
+ }
+
@Test
public void testVcnWithUnderlyingWifi() {
String testSsid = "Test VCN SSID";
@@ -266,11 +278,19 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
setWifiLevelForVcn(testLevel);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ true,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
@@ -391,13 +411,15 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
}
protected void setWifiLevelForVcn(int level) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ true,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
private int calculateRssiForLevel(int level) {
@@ -409,13 +431,15 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
}
protected void setWifiStateForVcn(boolean connected, String ssid) {
- when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
- when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
when(mWifiInfo.getSSID()).thenReturn(ssid);
when(mWifiInfo.isCarrierMerged()).thenReturn(true);
when(mWifiInfo.getSubscriptionId()).thenReturn(1);
setConnectivityViaCallbackInWifiTrackerForVcn(
- NetworkCapabilities.TRANSPORT_CELLULAR, false, connected, mVcnTransportInfo);
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ false,
+ connected,
+ mVcnTransportInfo,
+ newWifiNetwork(mWifiInfo));
}
protected void verifyLastQsDataDirection(boolean in, boolean out) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 7a8533ee6d69..fe287ef98729 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -23,6 +23,7 @@ import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.ContentResolver
+import android.content.Context
import android.content.pm.UserInfo
import android.database.ContentObserver
import android.graphics.drawable.Drawable
@@ -207,6 +208,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private val userHandleManaged: UserHandle = UserHandle(2)
private val userHandleSecondary: UserHandle = UserHandle(3)
+ @Mock private lateinit var userContextPrimary: Context
+ @Mock private lateinit var userContextSecondary: Context
+
private val userList = listOf(
mockUserInfo(userHandlePrimary, isManagedProfile = false),
mockUserInfo(userHandleManaged, isManagedProfile = true),
@@ -234,7 +238,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
- setActiveUser(userHandlePrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java)).thenReturn(
+ smartspaceManager
+ )
+
+ setActiveUser(userHandlePrimary, userContextPrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
setAllowPrivateNotifications(userHandleManaged, true)
setAllowPrivateNotifications(userHandleSecondary, true)
@@ -252,7 +260,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
controller = LockscreenSmartspaceController(
context,
featureFlags,
- smartspaceManager,
activityStarter,
falsingManager,
clock,
@@ -709,7 +716,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
connectSession()
// WHEN the secondary user becomes the active user
- setActiveUser(userHandleSecondary)
+ // Note: it doesn't switch to the SmartspaceManager for userContextSecondary
+ setActiveUser(userHandleSecondary, userContextSecondary)
userListener.onUserChanged(userHandleSecondary.identifier, context)
// WHEN we receive a new list of targets
@@ -912,9 +920,10 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
clearInvocations(smartspaceView)
}
- private fun setActiveUser(userHandle: UserHandle) {
+ private fun setActiveUser(userHandle: UserHandle, userContext: Context) {
`when`(userTracker.userId).thenReturn(userHandle.identifier)
`when`(userTracker.userHandle).thenReturn(userHandle)
+ `when`(userTracker.userContext).thenReturn(userContext)
}
private fun mockUserInfo(userHandle: UserHandle, isManagedProfile: Boolean): UserInfo {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 7d5278ed1601..eb1bcc7fe147 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -146,9 +146,17 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun activeRows_noRows_isEmpty() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+
+ assertThat(activeHeadsUpRows).isEmpty()
+ }
+
+ @Test
fun pinnedRows_noRows_isEmpty() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
assertThat(pinnedHeadsUpRows).isEmpty()
}
@@ -156,7 +164,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
@Test
fun pinnedRows_noPinnedRows_isEmpty() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN no rows are pinned
headsUpRepository.setNotifications(
fakeHeadsUpRowRepository("key 0"),
@@ -170,9 +178,27 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun activeRows_noPinnedRows_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN no rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0"),
+ fakeHeadsUpRowRepository("key 1"),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_hasPinnedRows_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN some rows are pinned
val rows =
arrayListOf(
@@ -188,9 +214,27 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun pinnedRows_hasPinnedRows_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN no rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsPinned_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// GIVEN some rows are pinned
val rows =
arrayListOf(
@@ -210,9 +254,34 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun activeRows_rowGetsPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // GIVEN some rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN all rows are present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+
+ // WHEN all rows gets pinned
+ rows[2].isPinned.value = true
+ runCurrent()
+
+ // THEN no change
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_allRowsPinned_containsAllRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// WHEN all rows are pinned
val rows =
arrayListOf(
@@ -228,9 +297,27 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun activeRows_allRowsPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // WHEN all rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2", isPinned = true),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // THEN no rows are filtered
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsUnPinned_containsPinnedRows() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
// GIVEN all rows are pinned
val rows =
arrayListOf(
@@ -250,9 +337,31 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
+ fun activeRows_rowGetsUnPinned_containsAllRows() =
+ testScope.runTest {
+ val activeHeadsUpRows by collectLastValue(underTest.activeHeadsUpRowKeys)
+ // GIVEN all rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0", isPinned = true),
+ fakeHeadsUpRowRepository("key 1", isPinned = true),
+ fakeHeadsUpRowRepository("key 2", isPinned = true),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // WHEN a row gets unpinned
+ rows[0].isPinned.value = false
+ runCurrent()
+
+ // THEN all rows are still present
+ assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
+ }
+
+ @Test
fun pinnedRows_rowGetsPinnedAndUnPinned_containsTheSameInstance() =
testScope.runTest {
- val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
+ val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRowKeys)
val rows =
arrayListOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 6b3fb5b4a2eb..503fa789cb80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -29,12 +29,13 @@ import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.in
import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePublicSingleLineView
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
-import com.android.systemui.util.mockito.mock
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
+import kotlin.test.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -69,7 +70,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
val publicView =
@@ -78,7 +79,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
assertNotNull(publicView)
@@ -114,7 +115,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
.addMessage(
"How about lunch?",
System.currentTimeMillis(),
- Person.Builder().setName("user2").build()
+ Person.Builder().setName("user2").build(),
)
.setGroupConversation(true)
notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
@@ -127,7 +128,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
as HybridConversationNotificationView
@@ -137,7 +138,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
entry = row.entry,
context = context,
- logger = mock()
+ logger = mock(),
)
as HybridConversationNotificationView
assertNotNull(publicView)
@@ -150,10 +151,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
systemUiContext = context,
)
// WHEN: binds the view
- SingleLineViewBinder.bind(
- viewModel,
- view,
- )
+ SingleLineViewBinder.bind(viewModel, view)
// THEN: the single-line conversation view should be bound with view model's corresponding
// fields
@@ -161,10 +159,55 @@ class SingleLineViewBinderTest : SysuiTestCase() {
assertEquals(viewModel.contentText, view.textView.text)
assertEquals(
viewModel.conversationData?.conversationSenderName,
- view.conversationSenderNameView.text
+ view.conversationSenderNameView.text,
)
}
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindConversationSingleLineView_nonConversationViewModel() {
+ // GIVEN: a ConversationSingleLineView, and a nonConversationViewModel
+ val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+ notificationBuilder.setStyle(style)
+ val notification = notificationBuilder.build()
+ val row: ExpandableNotificationRow = helper.createRow(notification)
+
+ val view =
+ inflatePrivateSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+
+ val publicView =
+ inflatePublicSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock(),
+ )
+ assertNotNull(publicView)
+
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = null,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+ // WHEN: binds the view with the view model
+ SingleLineViewBinder.bind(viewModel, view)
+
+ // THEN: the single-line view should be bound with view model's corresponding
+ // fields as a normal non-conversation single-line view
+ assertEquals(viewModel.titleText, view?.titleView?.text)
+ assertEquals(viewModel.contentText, view?.textView?.text)
+ assertNull(viewModel.conversationData)
+ }
+
private companion object {
const val CHANNEL_ID = "CHANNEL_ID"
const val CONTENT_TITLE = "A Cool New Feature"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 2ed34735db1f..83600422bda4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -160,11 +160,11 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_alarm_clock,
- ALARM_SLOT
+ ALARM_SLOT,
)
context.orCreateTestableResources.addOverride(
com.android.internal.R.string.status_bar_managed_profile,
- MANAGED_PROFILE_SLOT
+ MANAGED_PROFILE_SLOT,
)
whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
@@ -430,8 +430,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
eq(mContext.packageName),
eq(android.R.drawable.ic_lock_lock),
any(), // non-null
- eq("Bedtime Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Bedtime Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("bedtime")
@@ -443,8 +443,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
eq(null),
eq(android.R.drawable.ic_media_play),
any(), // non-null
- eq("Other Mode"),
- eq(StatusBarIcon.Shape.FIXED_SPACE)
+ eq("Other Mode is on"),
+ eq(StatusBarIcon.Shape.FIXED_SPACE),
)
zenModeRepository.deactivateMode("other")
@@ -538,7 +538,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
privacyLogger,
fakeConnectedDisplayStateProvider,
kosmos.zenModeInteractor,
- JavaAdapter(testScope.backgroundScope)
+ JavaAdapter(testScope.backgroundScope),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c5238194d0a4..81c40dc1778e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -36,7 +36,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
@@ -50,8 +52,11 @@ import com.android.systemui.statusbar.notification.row.NotificationContentView;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +76,14 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private ActivityStarter mActivityStarter;
+ @Mock private Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ @Mock private Lazy<SceneInteractor> mSceneInteractorLazy;
+ @Mock private JavaAdapter mJavaAdapter;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock private DeviceUnlockedInteractor mDeviceUnlockedInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
+
private int mCurrentUserId = 0;
private StatusBarRemoteInputCallback mRemoteInputCallback;
@@ -90,7 +101,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
mActivityStarter, mShadeController,
new CommandQueue(mContext, new FakeDisplayTracker(mContext)),
- mock(ActionClickLogger.class), mFakeExecutor));
+ mock(ActionClickLogger.class), mFakeExecutor, mDeviceUnlockedInteractorLazy,
+ mSceneInteractorLazy, mJavaAdapter));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index f62beeb16ae5..beba0f0c6286 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -133,10 +133,6 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
when(mUserTracker.getUserContext()).thenReturn(mContext);
- // Enable group volume adjustments
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
- true);
mCallback = mock(VolumeDialogControllerImpl.C.class);
mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 5d7e7c726c6c..1302faaf82ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -31,7 +31,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
provider: ComponentName,
user: UserHandle,
rank: Int?,
- configurator: WidgetConfigurator?
+ configurator: WidgetConfigurator?,
) {
coroutineScope.launch {
val id = nextWidgetId++
@@ -93,6 +93,22 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
_communalWidgets.value = fakeDatabase.values.toList()
}
+ override fun updateWidgetSpanY(widgetId: Int, spanY: Int) {
+ coroutineScope.launch {
+ fakeDatabase[widgetId]?.let { widget ->
+ when (widget) {
+ is CommunalWidgetContentModel.Available -> {
+ fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ }
+ is CommunalWidgetContentModel.Pending -> {
+ fakeDatabase[widgetId] = widget.copy(spanY = spanY)
+ }
+ }
+ _communalWidgets.value = fakeDatabase.values.toList()
+ }
+ }
+ }
+
override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {}
override fun abortRestoreWidgets() {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
index 15ed1b372db4..8422942a727a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SystemGestureExclusionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelKosmos.kt
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.scene.data.repository
+package com.android.systemui.communal.ui.viewmodel
-import android.view.windowManagerService
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.systemGestureExclusionRepository by Fixture {
- SystemGestureExclusionRepository(windowManager = windowManagerService)
-}
+val Kosmos.resizeableItemFrameViewModel by Kosmos.Fixture { ResizeableItemFrameViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
new file mode 100644
index 000000000000..83df5d874ad6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.display.data.repository
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+/** Fake [FocusedDisplayRepository] for testing. */
+class FakeFocusedDisplayRepository @Inject constructor() : FocusedDisplayRepository {
+ private val flow = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY)
+
+ override val focusedDisplayId: StateFlow<Int>
+ get() = flow.asStateFlow()
+
+ suspend fun emit(focusedDisplay: Int) = flow.emit(focusedDisplay)
+}
+
+@Module
+interface FakeFocusedDisplayRepositoryModule {
+ @Binds fun bindFake(fake: FakeFocusedDisplayRepository): FocusedDisplayRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
index b37cac1d36fd..ba316835f2b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
@@ -19,8 +19,10 @@ package com.android.systemui.keyboard.data.repository
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.shared.model.BacklightModel
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.filterNotNull
class FakeKeyboardRepository : KeyboardRepository {
@@ -32,8 +34,14 @@ class FakeKeyboardRepository : KeyboardRepository {
// filtering to make sure backlight doesn't have default initial value
override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull()
- private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
- override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
+ // implemented as channel because original implementation is modeling events: it doesn't hold
+ // state so it won't always emit once connected. And it's bad if some tests depend on that
+ // incorrect behaviour.
+ private val _newlyConnectedKeyboard: Channel<Keyboard> = Channel()
+ override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.consumeAsFlow()
+
+ private val _connectedKeyboards: MutableStateFlow<Set<Keyboard>> = MutableStateFlow(setOf())
+ override val connectedKeyboards: Flow<Set<Keyboard>> = _connectedKeyboards
fun setBacklight(state: BacklightModel) {
_backlightState.value = state
@@ -43,7 +51,14 @@ class FakeKeyboardRepository : KeyboardRepository {
_isAnyKeyboardConnected.value = connected
}
+ fun setConnectedKeyboards(keyboards: Set<Keyboard>) {
+ _connectedKeyboards.value = keyboards
+ _isAnyKeyboardConnected.value = keyboards.isNotEmpty()
+ }
+
fun setNewlyConnectedKeyboard(keyboard: Keyboard) {
- _newlyConnectedKeyboard.value = keyboard
+ _newlyConnectedKeyboard.trySend(keyboard)
+ _connectedKeyboards.value += keyboard
+ _isAnyKeyboardConnected.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index f26bb83ed3f0..805a710a2cf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -35,6 +35,8 @@ class FakeLightRevealScrimRepository : LightRevealScrimRepository {
private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
override val revealAmount: Flow<Float> = _revealAmount
+ val revealAnimatorRequests: MutableList<RevealAnimatorRequest> = arrayListOf()
+
override val isAnimating: Boolean
get() = false
@@ -44,5 +46,12 @@ class FakeLightRevealScrimRepository : LightRevealScrimRepository {
} else {
_revealAmount.value = 0.0f
}
+
+ revealAnimatorRequests.add(RevealAnimatorRequest(reveal, duration))
}
+
+ data class RevealAnimatorRequest(
+ val reveal: Boolean,
+ val duration: Long
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
new file mode 100644
index 000000000000..e4a2a874b848
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.plugins
+
+import android.media.AudioManager
+import android.media.AudioManager.CsdWarning
+import android.os.Handler
+import android.os.VibrationEffect
+import androidx.core.util.getOrElse
+import java.util.concurrent.CopyOnWriteArraySet
+
+class FakeVolumeDialogController(private val audioManager: AudioManager) : VolumeDialogController {
+
+ var isVisible: Boolean = false
+ private set
+
+ var hasScheduledTouchFeedback: Boolean = false
+ private set
+
+ var vibrationEffect: VibrationEffect? = null
+ private set
+
+ var hasUserActivity: Boolean = false
+ private set
+
+ private var hasVibrator: Boolean = true
+
+ private val state = VolumeDialogController.State()
+ private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>()
+
+ override fun setActiveStream(stream: Int) {
+ // ensure streamState existence for the active stream
+ state.states.getOrElse(stream) {
+ VolumeDialogController.StreamState().also { streamState ->
+ state.states.put(stream, streamState)
+ }
+ }
+ state.activeStream = stream
+ }
+
+ override fun setStreamVolume(stream: Int, userLevel: Int) {
+ val streamState =
+ state.states.getOrElse(stream) {
+ VolumeDialogController.StreamState().also { streamState ->
+ state.states.put(stream, streamState)
+ }
+ }
+ streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+ }
+
+ override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
+ if (external) {
+ state.ringerModeExternal = ringerModeNormal
+ } else {
+ state.ringerModeInternal = ringerModeNormal
+ }
+ }
+
+ fun setHasVibrator(hasVibrator: Boolean) {
+ this.hasVibrator = hasVibrator
+ }
+
+ override fun hasVibrator(): Boolean = hasVibrator
+
+ override fun vibrate(effect: VibrationEffect) {
+ vibrationEffect = effect
+ }
+
+ override fun scheduleTouchFeedback() {
+ hasScheduledTouchFeedback = true
+ }
+
+ fun resetScheduledTouchFeedback() {
+ hasScheduledTouchFeedback = false
+ }
+
+ override fun getAudioManager(): AudioManager = audioManager
+
+ override fun notifyVisible(visible: Boolean) {
+ isVisible = visible
+ }
+
+ override fun addCallback(callbacks: VolumeDialogController.Callbacks?, handler: Handler?) {
+ this.callbacks.add(callbacks)
+ }
+
+ override fun removeCallback(callbacks: VolumeDialogController.Callbacks?) {
+ this.callbacks.remove(callbacks)
+ }
+
+ override fun userActivity() {
+ hasUserActivity = true
+ }
+
+ fun resetUserActivity() {
+ hasUserActivity = false
+ }
+
+ override fun getState() {
+ callbacks.sendEvent { it.onStateChanged(state) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowRequested */
+ fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) {
+ callbacks.sendEvent { it.onShowRequested(reason, keyguardLocked, lockTaskModeState) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onDismissRequested */
+ fun onDismissRequested(reason: Int) {
+ callbacks.sendEvent { it.onDismissRequested(reason) }
+ }
+
+ /**
+ * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onLayoutDirectionChanged
+ */
+ fun onLayoutDirectionChanged(layoutDirection: Int) {
+ callbacks.sendEvent { it.onLayoutDirectionChanged(layoutDirection) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onConfigurationChanged */
+ fun onConfigurationChanged() {
+ callbacks.sendEvent { it.onConfigurationChanged() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowVibrateHint */
+ fun onShowVibrateHint() {
+ callbacks.sendEvent { it.onShowVibrateHint() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSilentHint */
+ fun onShowSilentHint() {
+ callbacks.sendEvent { it.onShowSilentHint() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onScreenOff */
+ fun onScreenOff() {
+ callbacks.sendEvent { it.onScreenOff() }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowSafetyWarning */
+ fun onShowSafetyWarning(flags: Int) {
+ callbacks.sendEvent { it.onShowSafetyWarning(flags) }
+ }
+
+ /**
+ * @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onAccessibilityModeChanged
+ */
+ fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+ callbacks.sendEvent { it.onAccessibilityModeChanged(showA11yStream) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onShowCsdWarning */
+ fun onShowCsdWarning(@CsdWarning csdWarning: Int, durationMs: Int) {
+ callbacks.sendEvent { it.onShowCsdWarning(csdWarning, durationMs) }
+ }
+
+ /** @see com.android.systemui.plugins.VolumeDialogController.Callbacks.onVolumeChangedFromKey */
+ fun onVolumeChangedFromKey() {
+ callbacks.sendEvent { it.onVolumeChangedFromKey() }
+ }
+
+ override fun getCaptionsEnabledState(checkForSwitchState: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+
+ override fun setCaptionsEnabledState(enabled: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+
+ override fun getCaptionsComponentState(fromTooltip: Boolean) {
+ error("Unsupported for the new Volume Dialog")
+ }
+}
+
+private inline fun CopyOnWriteArraySet<VolumeDialogController.Callbacks>.sendEvent(
+ event: (callback: VolumeDialogController.Callbacks) -> Unit
+) {
+ for (callback in this) {
+ event(callback)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
index 3e46c3f90b73..2f6d4fa32e65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SystemGestureExclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/VolumeDialogControllerKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.plugins
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.data.repository.systemGestureExclusionRepository
+import org.mockito.kotlin.mock
-val Kosmos.systemGestureExclusionInteractor by Fixture {
- SystemGestureExclusionInteractor(repository = systemGestureExclusionRepository)
-}
+val Kosmos.fakeVolumeDialogController by Kosmos.Fixture { FakeVolumeDialogController(mock {}) }
+var Kosmos.volumeDialogController: VolumeDialogController by
+ Kosmos.Fixture { fakeVolumeDialogController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index dbb3e386cc71..c218ff6ba966 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -48,6 +49,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by
configurationInteractor,
largeScreenHeaderHelper,
tileSquishinessInteractor,
+ paginatedGridViewModel,
lifecycleScope,
)
}
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 f842db4c0026..4f414d995aab 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
@@ -8,17 +8,14 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.interactor.systemGestureExclusionInteractor
import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
-import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -31,6 +28,7 @@ var Kosmos.sceneKeys by Fixture {
Scenes.Bouncer,
Scenes.Gone,
Scenes.Communal,
+ Scenes.Dream,
)
}
@@ -52,9 +50,10 @@ var Kosmos.sceneContainerConfig by Fixture {
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
- Scenes.Shade to 2,
- Scenes.QuickSettings to 3,
- Scenes.Bouncer to 4,
+ Scenes.Dream to 2,
+ Scenes.Shade to 3,
+ Scenes.QuickSettings to 4,
+ Scenes.Bouncer to 5,
)
SceneContainerConfig(
@@ -72,16 +71,15 @@ val Kosmos.transitionState by Fixture {
}
val Kosmos.sceneContainerViewModel by Fixture {
- sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply {
- setTransitionState(transitionState)
- }
+ sceneContainerViewModelFactory
+ .create(mock<View>()) {}
+ .apply { setTransitionState(transitionState) }
}
val Kosmos.sceneContainerViewModelFactory by Fixture {
object : SceneContainerViewModel.Factory {
override fun create(
view: View,
- displayId: Int,
motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
): SceneContainerViewModel =
SceneContainerViewModel(
@@ -91,26 +89,13 @@ val Kosmos.sceneContainerViewModelFactory by Fixture {
shadeInteractor = shadeInteractor,
splitEdgeDetector = splitEdgeDetector,
logger = sceneLogger,
- gestureFilterFactory = sceneContainerGestureFilterFactory,
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
- displayId = displayId,
motionEventHandlerReceiver = motionEventHandlerReceiver,
)
}
}
-val Kosmos.sceneContainerGestureFilterFactory by Fixture {
- object : SceneContainerGestureFilter.Factory {
- override fun create(displayId: Int): SceneContainerGestureFilter {
- return SceneContainerGestureFilter(
- interactor = systemGestureExclusionInteractor,
- displayId = displayId,
- )
- }
- }
-}
-
val Kosmos.sceneContainerHapticsViewModelFactory by Fixture {
object : SceneContainerHapticsViewModel.Factory {
override fun create(view: View): SceneContainerHapticsViewModel {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
new file mode 100644
index 000000000000..db9c48d9be6f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.os.Handler
+import android.os.looper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.volumeDialogController
+
+val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
+ Kosmos.Fixture {
+ VolumeDialogCallbacksInteractor(
+ volumeDialogController = volumeDialogController,
+ coroutineScope = applicationCoroutineScope,
+ bgHandler = Handler(looper),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
new file mode 100644
index 000000000000..e73539eac6f1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.volumeDialogVisibilityInteractor by
+ Kosmos.Fixture {
+ VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
+ }
diff --git a/packages/SystemUI/frp/Android.bp b/packages/SystemUI/utils/kairos/Android.bp
index c3381db7a0e3..1442591eab99 100644
--- a/packages/SystemUI/frp/Android.bp
+++ b/packages/SystemUI/utils/kairos/Android.bp
@@ -20,9 +20,9 @@ package {
}
java_library {
- name: "kt-frp",
+ name: "kairos",
host_supported: true,
- kotlincflags: ["-opt-in=com.android.systemui.experimental.frp.ExperimentalFrpApi"],
+ kotlincflags: ["-opt-in=com.android.systemui.kairos.ExperimentalFrpApi"],
srcs: ["src/**/*.kt"],
static_libs: [
"kotlin-stdlib",
@@ -31,7 +31,7 @@ java_library {
}
java_test {
- name: "kt-frp-test",
+ name: "kairos-test",
optimize: {
enabled: false,
},
@@ -39,7 +39,7 @@ java_test {
"test/**/*.kt",
],
static_libs: [
- "kt-frp",
+ "kairos",
"junit",
"kotlin-stdlib",
"kotlin-test",
diff --git a/packages/SystemUI/frp/OWNERS b/packages/SystemUI/utils/kairos/OWNERS
index 8876ad6f6224..8876ad6f6224 100644
--- a/packages/SystemUI/frp/OWNERS
+++ b/packages/SystemUI/utils/kairos/OWNERS
diff --git a/packages/SystemUI/frp/README.md b/packages/SystemUI/utils/kairos/README.md
index 9c5bdb036c45..85f622ca05f3 100644
--- a/packages/SystemUI/frp/README.md
+++ b/packages/SystemUI/utils/kairos/README.md
@@ -1,4 +1,4 @@
-# kt-frp
+# Kairos
A functional reactive programming (FRP) library for Kotlin.
@@ -13,12 +13,12 @@ FRP exposes an API that should be familiar to those versed in Kotlin `Flow`.
### Details for nerds
-`kt-frp` implements an applicative / monadic flavor of FRP, using a push-pull
+`Kairos` implements an applicative / monadic flavor of FRP, using a push-pull
methodology to allow for efficient updates.
"Real" functional reactive programming should be specified with denotational
semantics ([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)):
-you can view the semantics for `kt-frp` [here](docs/semantics.md).
+you can view the semantics for `Kairos` [here](docs/semantics.md).
## Usage
@@ -61,4 +61,4 @@ will tear-down all effects and obervers running within the lambda.
## Resources
-- [Cheatsheet for those coming from Kotlin Flow](docs/flow-to-frp-cheatsheet.md)
+- [Cheatsheet for those coming from Kotlin Flow](docs/flow-to-kairos-cheatsheet.md)
diff --git a/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md b/packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md
index e20f3e6c7461..9f7fd022f019 100644
--- a/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md
+++ b/packages/SystemUI/utils/kairos/docs/flow-to-kairos-cheatsheet.md
@@ -1,27 +1,27 @@
-# From Flows to FRP
+# From Flows to Kairos
## Key differences
-* FRP evaluates all events (`TFlow` emissions + observers) in a transaction.
+* Kairos evaluates all events (`TFlow` emissions + observers) in a transaction.
-* FRP splits `Flow` APIs into two distinct types: `TFlow` and `TState`
+* Kairos splits `Flow` APIs into two distinct types: `TFlow` and `TState`
* `TFlow` is roughly equivalent to `SharedFlow` w/ a replay cache that
- exists for the duration of the current FRP transaction and shared with
+ exists for the duration of the current Kairos transaction and shared with
`SharingStarted.WhileSubscribed()`
* `TState` is roughly equivalent to `StateFlow` shared with
`SharingStarted.Eagerly`, but the current value can only be queried within
- a FRP transaction, and the value is only updated at the end of the
+ a Kairos transaction, and the value is only updated at the end of the
transaction
-* FRP further divides `Flow` APIs based on how they internally use state:
+* Kairos further divides `Flow` APIs based on how they internally use state:
* **FrpTransactionScope:** APIs that internally query some state need to be
- performed within an FRP transaction
+ performed within an Kairos transaction
* this scope is available from the other scopes, and from most lambdas
- passed to other FRP APIs
+ passed to other Kairos APIs
* **FrpStateScope:** APIs that internally accumulate state in reaction to
events need to be performed within an FRP State scope (akin to a
@@ -126,8 +126,8 @@ has emitted at least once. This often bites developers. As a workaround,
developers generally append `.onStart { emit(initialValue) }` to the `Flows`
that don't immediately emit.
-FRP avoids this gotcha by forcing usage of `TState` for `combine`, thus ensuring
-that there is always a current value to be combined for each input.
+Kairos avoids this gotcha by forcing usage of `TState` for `combine`, thus
+ensuring that there is always a current value to be combined for each input.
## collect { … }
@@ -158,8 +158,8 @@ tFlow.map { tState.sample() }
#### Explanation
To keep all state-reads consistent, the current value of a TState can only be
-queried within an FRP transaction, modeled with `FrpTransactionScope`. Note that
-both `FrpStateScope` and `FrpBuildScope` extend `FrpTransactionScope`.
+queried within a Kairos transaction, modeled with `FrpTransactionScope`. Note
+that both `FrpStateScope` and `FrpBuildScope` extend `FrpTransactionScope`.
### I want to sample a TFlow
@@ -198,7 +198,7 @@ simultaneous emissions within the same transaction.
#### Explanation
-Under FRP's rules, a `TFlow` may only emit up to once per transaction. This
+Under Kairos's rules, a `TFlow` may only emit up to once per transaction. This
means that if we are merging two or more `TFlows` that are emitting at the same
time (within the same transaction), the resulting merged `TFlow` must emit a
single value. The lambda argument allows the developer to decide what to do in
diff --git a/packages/SystemUI/frp/docs/semantics.md b/packages/SystemUI/utils/kairos/docs/semantics.md
index b533190e687d..d43bb4447061 100644
--- a/packages/SystemUI/frp/docs/semantics.md
+++ b/packages/SystemUI/utils/kairos/docs/semantics.md
@@ -1,11 +1,11 @@
# FRP Semantics
-`kt-frp`'s pure API is based off of the following denotational semantics
+`Kairos`'s pure API is based off of the following denotational semantics
([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)).
-The semantics model `kt-frp` types as time-varying values; by making `Time` a
+The semantics model `Kairos` types as time-varying values; by making `Time` a
first-class value, we can define a referentially-transparent API that allows us
-to reason about the behavior of the pure FRP combinators. This is
+to reason about the behavior of the pure `Kairos` combinators. This is
implementation-agnostic; we can compare the behavior of any implementation with
expected behavior denoted by these semantics to identify bugs.
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
index 298c071a4229..8bf3a43765ae 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.util.These
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.These
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
index 6e4c9eba90bf..4de6deb3dc53 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
@@ -16,11 +16,11 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.RestrictsSuspension
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
index a8ec98fdc6c9..be2eb4312476 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
index acc76d93f928..b688eafe12e9 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.BuildScopeImpl
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.StateScopeImpl
-import com.android.systemui.experimental.frp.internal.util.awaitCancellationAndThen
-import com.android.systemui.experimental.frp.internal.util.childScope
+import com.android.systemui.kairos.internal.BuildScopeImpl
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.StateScopeImpl
+import com.android.systemui.kairos.internal.util.awaitCancellationAndThen
+import com.android.systemui.kairos.internal.util.childScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt
index a5a7977f2177..ad6b2c8d04eb 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
import kotlin.coroutines.resume
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt
index 61336f4f4608..c7ea6808a53e 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpStateScope.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
-
-import com.android.systemui.experimental.frp.combine as combinePure
-import com.android.systemui.experimental.frp.map as mapPure
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.WithPrev
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.none
-import com.android.systemui.experimental.frp.util.partitionEithers
-import com.android.systemui.experimental.frp.util.zipWith
+package com.android.systemui.kairos
+
+import com.android.systemui.kairos.combine as combinePure
+import com.android.systemui.kairos.map as mapPure
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.WithPrev
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.none
+import com.android.systemui.kairos.util.partitionEithers
+import com.android.systemui.kairos.util.zipWith
import kotlin.coroutines.RestrictsSuspension
typealias FrpStateful<R> = suspend FrpStateScope.() -> R
@@ -715,7 +715,7 @@ interface FrpStateScope : FrpTransactionScope {
stateB: TState<B>,
transform: suspend FrpTransactionScope.(A, B) -> Z,
): TState<Z> =
- com.android.systemui.experimental.frp
+ com.android.systemui.kairos
.combine(stateA, stateB) { a, b -> transactionally { transform(a, b) } }
.sampleTransactionals()
@@ -733,7 +733,7 @@ interface FrpStateScope : FrpTransactionScope {
stateD: TState<D>,
transform: suspend FrpTransactionScope.(A, B, C, D) -> Z,
): TState<Z> =
- com.android.systemui.experimental.frp
+ com.android.systemui.kairos
.combine(stateA, stateB, stateC, stateD) { a, b, c, d ->
transactionally { transform(a, b, c, d) }
}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt
index b0b9dbcbe8c1..a7ae1d9646b3 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpTransactionScope.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
import kotlin.coroutines.RestrictsSuspension
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
index cca6c9a623f2..7ba1aca31eae 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
@@ -14,36 +14,36 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
-
-import com.android.systemui.experimental.frp.internal.DemuxImpl
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.InputNode
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.TFlowImpl
-import com.android.systemui.experimental.frp.internal.activated
-import com.android.systemui.experimental.frp.internal.cached
-import com.android.systemui.experimental.frp.internal.constInit
-import com.android.systemui.experimental.frp.internal.filterNode
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.map
-import com.android.systemui.experimental.frp.internal.mapImpl
-import com.android.systemui.experimental.frp.internal.mapMaybeNode
-import com.android.systemui.experimental.frp.internal.mergeNodes
-import com.android.systemui.experimental.frp.internal.mergeNodesLeft
-import com.android.systemui.experimental.frp.internal.neverImpl
-import com.android.systemui.experimental.frp.internal.switchDeferredImplSingle
-import com.android.systemui.experimental.frp.internal.switchPromptImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Either
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.toMaybe
+package com.android.systemui.kairos
+
+import com.android.systemui.kairos.internal.DemuxImpl
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.InputNode
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.TFlowImpl
+import com.android.systemui.kairos.internal.activated
+import com.android.systemui.kairos.internal.cached
+import com.android.systemui.kairos.internal.constInit
+import com.android.systemui.kairos.internal.filterNode
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.map
+import com.android.systemui.kairos.internal.mapImpl
+import com.android.systemui.kairos.internal.mapMaybeNode
+import com.android.systemui.kairos.internal.mergeNodes
+import com.android.systemui.kairos.internal.mergeNodesLeft
+import com.android.systemui.kairos.internal.neverImpl
+import com.android.systemui.kairos.internal.switchDeferredImplSingle
+import com.android.systemui.kairos.internal.switchPromptImpl
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.toMaybe
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty
import kotlinx.coroutines.CompletableDeferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
index a5ec503e5c8d..a4c695657f8d 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
@@ -14,29 +14,29 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
-
-import com.android.systemui.experimental.frp.internal.DerivedMapCheap
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.Network
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.Schedulable
-import com.android.systemui.experimental.frp.internal.TFlowImpl
-import com.android.systemui.experimental.frp.internal.TStateImpl
-import com.android.systemui.experimental.frp.internal.TStateSource
-import com.android.systemui.experimental.frp.internal.activated
-import com.android.systemui.experimental.frp.internal.cached
-import com.android.systemui.experimental.frp.internal.constInit
-import com.android.systemui.experimental.frp.internal.constS
-import com.android.systemui.experimental.frp.internal.filterNode
-import com.android.systemui.experimental.frp.internal.flatMap
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.map
-import com.android.systemui.experimental.frp.internal.mapCheap
-import com.android.systemui.experimental.frp.internal.mapImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.zipStates
+package com.android.systemui.kairos
+
+import com.android.systemui.kairos.internal.DerivedMapCheap
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.Network
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.Schedulable
+import com.android.systemui.kairos.internal.TFlowImpl
+import com.android.systemui.kairos.internal.TStateImpl
+import com.android.systemui.kairos.internal.TStateSource
+import com.android.systemui.kairos.internal.activated
+import com.android.systemui.kairos.internal.cached
+import com.android.systemui.kairos.internal.constInit
+import com.android.systemui.kairos.internal.constS
+import com.android.systemui.kairos.internal.filterNode
+import com.android.systemui.kairos.internal.flatMap
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.map
+import com.android.systemui.kairos.internal.mapCheap
+import com.android.systemui.kairos.internal.mapImpl
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.zipStates
import kotlin.reflect.KProperty
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
index 0e7b420afd51..6b1c8c8fc3e5 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp
+package com.android.systemui.kairos
-import com.android.systemui.experimental.frp.internal.InitScope
-import com.android.systemui.experimental.frp.internal.NoScope
-import com.android.systemui.experimental.frp.internal.TransactionalImpl
-import com.android.systemui.experimental.frp.internal.init
-import com.android.systemui.experimental.frp.internal.transactionalImpl
-import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.kairos.internal.InitScope
+import com.android.systemui.kairos.internal.NoScope
+import com.android.systemui.kairos.internal.TransactionalImpl
+import com.android.systemui.kairos.internal.init
+import com.android.systemui.kairos.internal.transactionalImpl
+import com.android.systemui.kairos.internal.util.hashString
import kotlinx.coroutines.CompletableDeferred
/**
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
index 806234184d81..4f302a14ff00 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.debug
-
-import com.android.systemui.experimental.frp.MutableTState
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.TStateLoop
-import com.android.systemui.experimental.frp.internal.DerivedFlatten
-import com.android.systemui.experimental.frp.internal.DerivedMap
-import com.android.systemui.experimental.frp.internal.DerivedMapCheap
-import com.android.systemui.experimental.frp.internal.DerivedZipped
-import com.android.systemui.experimental.frp.internal.Init
-import com.android.systemui.experimental.frp.internal.TStateDerived
-import com.android.systemui.experimental.frp.internal.TStateImpl
-import com.android.systemui.experimental.frp.internal.TStateSource
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.none
-import com.android.systemui.experimental.frp.util.orElseGet
+package com.android.systemui.kairos.debug
+
+import com.android.systemui.kairos.MutableTState
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.TStateLoop
+import com.android.systemui.kairos.internal.DerivedFlatten
+import com.android.systemui.kairos.internal.DerivedMap
+import com.android.systemui.kairos.internal.DerivedMapCheap
+import com.android.systemui.kairos.internal.DerivedZipped
+import com.android.systemui.kairos.internal.Init
+import com.android.systemui.kairos.internal.TStateDerived
+import com.android.systemui.kairos.internal.TStateImpl
+import com.android.systemui.kairos.internal.TStateSource
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.none
+import com.android.systemui.kairos.util.orElseGet
// object IdGen {
// private val counter = AtomicLong()
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
index 127abd857fb3..90f1aea3e42f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
@@ -14,34 +14,34 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.CoalescingMutableTFlow
-import com.android.systemui.experimental.frp.FrpBuildScope
-import com.android.systemui.experimental.frp.FrpCoalescingProducerScope
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpEffectScope
-import com.android.systemui.experimental.frp.FrpNetwork
-import com.android.systemui.experimental.frp.FrpProducerScope
-import com.android.systemui.experimental.frp.FrpSpec
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.GroupedTFlow
-import com.android.systemui.experimental.frp.LocalFrpNetwork
-import com.android.systemui.experimental.frp.MutableTFlow
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.groupByKey
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.internal.util.childScope
-import com.android.systemui.experimental.frp.internal.util.launchOnCancel
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.launchEffect
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.CoalescingMutableTFlow
+import com.android.systemui.kairos.FrpBuildScope
+import com.android.systemui.kairos.FrpCoalescingProducerScope
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpEffectScope
+import com.android.systemui.kairos.FrpNetwork
+import com.android.systemui.kairos.FrpProducerScope
+import com.android.systemui.kairos.FrpSpec
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.GroupedTFlow
+import com.android.systemui.kairos.LocalFrpNetwork
+import com.android.systemui.kairos.MutableTFlow
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.internal.util.childScope
+import com.android.systemui.kairos.internal.util.launchOnCancel
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.launchEffect
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt
index f72ba5fdfc6f..f65307c6106f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/DeferScope.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.asyncImmediate
-import com.android.systemui.experimental.frp.internal.util.launchImmediate
+import com.android.systemui.kairos.internal.util.asyncImmediate
+import com.android.systemui.kairos.internal.util.launchImmediate
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt
index 418220f2da4d..e7b99528fdfc 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Demux.kt
@@ -16,13 +16,13 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.flatMap
-import com.android.systemui.experimental.frp.util.getMaybe
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.flatMap
+import com.android.systemui.kairos.util.getMaybe
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt
index 38bc22f1df80..815473fe900f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.TFlowLoop
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.Transactional
-import com.android.systemui.experimental.frp.emptyTFlow
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.mapCheap
-import com.android.systemui.experimental.frp.switch
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.TFlowLoop
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.Transactional
+import com.android.systemui.kairos.emptyTFlow
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.switch
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt
index 4f2a76999bb6..bc06a3679d5c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/FilterNode.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
internal inline fun <A, B> mapMaybeNode(
crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index 9425870738fc..3aec319881d0 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Bag
+import com.android.systemui.kairos.internal.util.Bag
import java.util.TreeMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt
index efb7a0951737..57db9a493e21 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Init.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt
index 85c87fea299b..8efaf79b18b2 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Inputs.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
index b6cd9063622b..af864e6c3496 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.FrpBuildScope
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.internal.util.HeteroMap
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.FrpBuildScope
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.internal.util.HeteroMap
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
internal interface InitScope {
val networkId: Any
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
index e616d625dd1c..f7ff15f0507b 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
@@ -16,11 +16,11 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.ConcurrentNullableHashMap
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.kairos.internal.util.ConcurrentNullableHashMap
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.util.Just
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index 6d43285c9ef5..08bee855831a 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -14,28 +14,28 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.associateByIndexTo
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.util.mapParallel
-import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.These
-import com.android.systemui.experimental.frp.util.flatMap
-import com.android.systemui.experimental.frp.util.getMaybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.maybeThat
-import com.android.systemui.experimental.frp.util.maybeThis
-import com.android.systemui.experimental.frp.util.merge
-import com.android.systemui.experimental.frp.util.orElseGet
-import com.android.systemui.experimental.frp.util.partitionEithers
-import com.android.systemui.experimental.frp.util.these
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.associateByIndexTo
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.util.mapParallel
+import com.android.systemui.kairos.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.These
+import com.android.systemui.kairos.util.flatMap
+import com.android.systemui.kairos.util.getMaybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.maybeThat
+import com.android.systemui.kairos.util.maybeThis
+import com.android.systemui.kairos.util.merge
+import com.android.systemui.kairos.util.orElseGet
+import com.android.systemui.kairos.util.partitionEithers
+import com.android.systemui.kairos.util.these
import java.util.TreeMap
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index ea0c1501f6a4..cdfafa943121 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.launchImmediate
-import com.android.systemui.experimental.frp.internal.util.mapParallel
-import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.filterJust
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.partitionEithers
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.launchImmediate
+import com.android.systemui.kairos.internal.util.mapParallel
+import com.android.systemui.kairos.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.filterJust
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.partitionEithers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
index b5ffe75eb9f4..f0df89d780c9 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.internal.util.HeteroMap
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.internal.util.HeteroMap
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.ConcurrentLinkedQueue
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt
index 6375918f35cd..fbd9689eb1d0 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NoScope.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpScope
+import com.android.systemui.kairos.FrpScope
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt
index e7f76a08b638..000240796a82 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/NodeTypes.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.kairos.util.Maybe
/*
Dmux
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt
index e60dccaac392..a3af2d304f7f 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.kairos.util.Just
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt
index b4656e0aee3a..dac98e0e807c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/PullNodes.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.map
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
index 4fef865e87e7..872fb7a6cb74 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt
index c1d10760978a..baf4101d52ef 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/StateScopeImpl.kt
@@ -14,27 +14,27 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.FrpDeferredValue
-import com.android.systemui.experimental.frp.FrpStateScope
-import com.android.systemui.experimental.frp.FrpStateful
-import com.android.systemui.experimental.frp.FrpTransactionScope
-import com.android.systemui.experimental.frp.GroupedTFlow
-import com.android.systemui.experimental.frp.TFlow
-import com.android.systemui.experimental.frp.TFlowInit
-import com.android.systemui.experimental.frp.TFlowLoop
-import com.android.systemui.experimental.frp.TState
-import com.android.systemui.experimental.frp.TStateInit
-import com.android.systemui.experimental.frp.emptyTFlow
-import com.android.systemui.experimental.frp.groupByKey
-import com.android.systemui.experimental.frp.init
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.mapCheap
-import com.android.systemui.experimental.frp.merge
-import com.android.systemui.experimental.frp.switch
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.kairos.FrpDeferredValue
+import com.android.systemui.kairos.FrpStateScope
+import com.android.systemui.kairos.FrpStateful
+import com.android.systemui.kairos.FrpTransactionScope
+import com.android.systemui.kairos.GroupedTFlow
+import com.android.systemui.kairos.TFlow
+import com.android.systemui.kairos.TFlowInit
+import com.android.systemui.kairos.TFlowLoop
+import com.android.systemui.kairos.TState
+import com.android.systemui.kairos.TStateInit
+import com.android.systemui.kairos.emptyTFlow
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.init
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.merge
+import com.android.systemui.kairos.switch
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.map
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt
index 79978640d5a8..b904b48f7f9c 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TFlowImpl.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.kairos.util.Maybe
/* Initialized TFlow */
internal fun interface TFlowImpl<out A> {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
index d8b6dac000bb..5cec05c8ef2d 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
-
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.associateByIndex
-import com.android.systemui.experimental.frp.internal.util.hashString
-import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
-import com.android.systemui.experimental.frp.util.Just
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.none
+package com.android.systemui.kairos.internal
+
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.associateByIndex
+import com.android.systemui.kairos.internal.util.hashString
+import com.android.systemui.kairos.internal.util.mapValuesParallel
+import com.android.systemui.kairos.util.Just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.none
import java.util.concurrent.atomic.AtomicLong
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt
index c3f80a179684..8647bdd5b7b1 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TransactionalImpl.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal
+package com.android.systemui.kairos.internal
-import com.android.systemui.experimental.frp.internal.util.Key
-import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.kairos.internal.util.Key
+import com.android.systemui.kairos.internal.util.hashString
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt
index cc5538e5c87c..47185195a2bb 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Bag.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
internal class Bag<T> private constructor(private val intMap: MutableMap<T, Int>) :
Set<T> by intMap.keys {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt
index 449aa19be635..6c8ae7cf6436 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/ConcurrentNullableHashMap.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import java.util.concurrent.ConcurrentHashMap
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt
index 14a567ca67c8..5cee2dd5880a 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/HeteroMap.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.just
import java.util.concurrent.ConcurrentHashMap
internal interface Key<A>
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt
index 6f19a76f8900..ebf9a66be0ae 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/MapUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt
index 0a47429d4113..6bb7f9f593aa 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/util/Util.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.experimental.frp.internal.util
+package com.android.systemui.kairos.internal.util
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt
index dca8364ed8ef..ad9f7d715156 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Either.kt
@@ -16,7 +16,7 @@
@file:Suppress("NOTHING_TO_INLINE")
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/**
* Contains a value of two possibilities: `Left<A>` or `Right<B>`
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
index 59c680e91a52..c3cae3885bd3 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
@@ -16,7 +16,7 @@
@file:Suppress("NOTHING_TO_INLINE", "SuspendCoroutine")
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt
index 5404c0795af8..aa95e0d2dc1b 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/These.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/** Contains at least one of two potential values. */
sealed class These<A, B> {
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt
index e52a6e166ec5..5cfaa3ea2801 100644
--- a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/WithPrev.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.experimental.frp.util
+package com.android.systemui.kairos.util
/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
diff --git a/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
index a58f4995138a..165230b2aeaf 100644
--- a/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt
+++ b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
@@ -16,17 +16,17 @@
@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFrpApi::class)
-package com.android.systemui.experimental.frp
-
-import com.android.systemui.experimental.frp.util.Either
-import com.android.systemui.experimental.frp.util.Left
-import com.android.systemui.experimental.frp.util.Maybe
-import com.android.systemui.experimental.frp.util.None
-import com.android.systemui.experimental.frp.util.Right
-import com.android.systemui.experimental.frp.util.just
-import com.android.systemui.experimental.frp.util.map
-import com.android.systemui.experimental.frp.util.maybe
-import com.android.systemui.experimental.frp.util.none
+package com.android.systemui.kairos
+
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Left
+import com.android.systemui.kairos.util.Maybe
+import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.Right
+import com.android.systemui.kairos.util.just
+import com.android.systemui.kairos.util.map
+import com.android.systemui.kairos.util.maybe
+import com.android.systemui.kairos.util.none
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
@@ -55,7 +55,7 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
-class FrpTests {
+class KairosTests {
@Test
fun basic() = runFrpTest { network ->
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 994bdb58f543..648990588d29 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -206,7 +206,7 @@ message SystemMessage {
// Inform that DND settings have changed on OS upgrade
// Package: android
- NOTE_ZEN_UPGRADE = 48;
+ NOTE_ZEN_UPGRADE = 48 [deprecated = true];
// Notification to suggest automatic battery saver.
// Package: android
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index ef795c63880e..520f050f0655 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -37,8 +37,6 @@ public class RavenwoodCommonUtils {
private RavenwoodCommonUtils() {
}
- private static final Object sLock = new Object();
-
/**
* If set to "1", we enable the verbose logging.
*
@@ -68,9 +66,6 @@ public class RavenwoodCommonUtils {
public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
- // @GuardedBy("sLock")
- private static boolean sIntegrityChecked = false;
-
/**
* @return if we're running on Ravenwood.
*/
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 4e7dc5d6264f..ad86135de32e 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -32,25 +32,20 @@ public class NativeAllocationRegistry {
public static NativeAllocationRegistry createNonmalloced(
ClassLoader classLoader, long freeFunction, long size) {
- return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
+ return new NativeAllocationRegistry(classLoader, freeFunction, size);
}
public static NativeAllocationRegistry createMalloced(
ClassLoader classLoader, long freeFunction, long size) {
- return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
+ return new NativeAllocationRegistry(classLoader, freeFunction, size);
}
public static NativeAllocationRegistry createMalloced(
ClassLoader classLoader, long freeFunction) {
- return new NativeAllocationRegistry(classLoader, freeFunction, 0, true);
+ return new NativeAllocationRegistry(classLoader, freeFunction, 0);
}
public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
- this(classLoader, freeFunction, size, size == 0);
- }
-
- private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
- boolean mallocAllocation) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index e897735493a3..ac499b966afe 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -54,32 +54,34 @@ android_ravenwood_test {
auto_gen_config: true,
}
-android_test {
- name: "RavenwoodBivalentTest_device",
+// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
- srcs: [
- "test/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
-
- "androidx.annotation_annotation",
- "androidx.test.ext.junit",
- "androidx.test.rules",
-
- "junit-params",
- "platform-parametric-runner-lib",
-
- "ravenwood-junit",
- ],
- jni_libs: [
- "libravenwoodbivalenttest_jni",
- ],
- test_suites: [
- "device-tests",
- ],
- optimize: {
- enabled: false,
- },
-}
+// android_test {
+// name: "RavenwoodBivalentTest_device",
+//
+// srcs: [
+// "test/**/*.java",
+// ],
+// static_libs: [
+// "junit",
+// "truth",
+//
+// "androidx.annotation_annotation",
+// "androidx.test.ext.junit",
+// "androidx.test.rules",
+//
+// "junit-params",
+// "platform-parametric-runner-lib",
+//
+// "ravenwood-junit",
+// ],
+// jni_libs: [
+// "libravenwoodbivalenttest_jni",
+// ],
+// test_suites: [
+// "device-tests",
+// ],
+// optimize: {
+// enabled: false,
+// },
+// }
diff --git a/ravenwood/bivalenttest/AndroidManifest.xml b/ravenwood/tests/bivalenttest/AndroidManifest.xml
index 474c03f4f3c9..474c03f4f3c9 100644
--- a/ravenwood/bivalenttest/AndroidManifest.xml
+++ b/ravenwood/tests/bivalenttest/AndroidManifest.xml
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTest.xml
index 9e5dd11b60cb..9e5dd11b60cb 100644
--- a/ravenwood/bivalenttest/AndroidTest.xml
+++ b/ravenwood/tests/bivalenttest/AndroidTest.xml
diff --git a/ravenwood/bivalenttest/README.md b/ravenwood/tests/bivalenttest/README.md
index 71535556ec75..71535556ec75 100644
--- a/ravenwood/bivalenttest/README.md
+++ b/ravenwood/tests/bivalenttest/README.md
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 956d79c0d25f..956d79c0d25f 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/tests/bivalenttest/jni/ravenwood_core_test_jni.cpp
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
index d91e73438fa3..d91e73438fa3 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
index e8f59db86901..e8f59db86901 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
index a5a16c14600b..a5a16c14600b 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 0cc2adc6b26b..0cc2adc6b26b 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
index c25d2b4cbc4d..c25d2b4cbc4d 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
index 415b4676f46b..415b4676f46b 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
index d47330568828..d47330568828 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNoConfigNoRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 3de372e48e3a..3de372e48e3a 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
index 09a0aa8dbaa2..09a0aa8dbaa2 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
index d7c2c6cd73a8..d7c2c6cd73a8 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
index 7ef672e80bee..7ef672e80bee 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
index 7ef40dc49e1a..7ef40dc49e1a 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
index ae596b10848b..ae596b10848b 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
index 1ca97af632dd..1ca97af632dd 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
index 9d878f444e5e..9d878f444e5e 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
index c77841b1b55a..c77841b1b55a 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
index ea1a29d57482..ea1a29d57482 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
index c042eb010558..c042eb010558 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
index 2feb5ba9aa01..2feb5ba9aa01 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
index 7e3bc0fccd7f..7e3bc0fccd7f 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
index 7e396c2080eb..7e396c2080eb 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index d94475c00240..85f1baf1cab9 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -17,9 +17,15 @@ android_ravenwood_test {
"junit-params",
"platform-parametric-runner-lib",
"truth",
+
+ // This library should be removed by Ravenizer
+ "mockito-target-minus-junit4",
],
srcs: [
"test/**/*.java",
],
+ ravenizer: {
+ strip_mockito: true,
+ },
auto_gen_config: true,
}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
new file mode 100644
index 000000000000..dd6d259d5a34
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMockitoTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class RavenwoodMockitoTest {
+
+ @Test
+ public void checkMockitoClasses() {
+ // DexMaker should not exist
+ assertThrows(
+ ClassNotFoundException.class,
+ () -> Class.forName("com.android.dx.DexMaker"));
+ // Mockito 2 should not exist
+ assertThrows(
+ ClassNotFoundException.class,
+ () -> Class.forName("org.mockito.Matchers"));
+ }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index f7f9a8563656..49f0b599762f 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -85,18 +85,17 @@ data class RavenizerStats(
/**
* Main class.
*/
-class Ravenizer(val options: RavenizerOptions) {
- fun run() {
+class Ravenizer {
+ fun run(options: RavenizerOptions) {
val stats = RavenizerStats()
- val fatalValidation = options.fatalValidation.get
-
stats.totalTime = log.nTime {
process(
options.inJar.get,
options.outJar.get,
options.enableValidation.get,
- fatalValidation,
+ options.fatalValidation.get,
+ options.stripMockito.get,
stats,
)
}
@@ -108,6 +107,7 @@ class Ravenizer(val options: RavenizerOptions) {
outJar: String,
enableValidation: Boolean,
fatalValidation: Boolean,
+ stripMockito: Boolean,
stats: RavenizerStats,
) {
var allClasses = ClassNodes.loadClassStructures(inJar) {
@@ -126,6 +126,9 @@ class Ravenizer(val options: RavenizerOptions) {
}
}
}
+ if (includeUnsupportedMockito(allClasses)) {
+ log.w("Unsupported Mockito detected in $inJar}!")
+ }
stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
ZipFile(inJar).use { inZip ->
@@ -145,6 +148,11 @@ class Ravenizer(val options: RavenizerOptions) {
)
}
+ if (stripMockito && entry.name.isMockitoFile()) {
+ // Skip this entry
+ continue
+ }
+
val className = zipEntryNameToClassName(entry.name)
if (className != null) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
index ff41818cd370..aee453020fb4 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -36,6 +36,6 @@ fun main(args: Array<String>) {
log.v("Options: $options")
// Run.
- Ravenizer(options).run()
+ Ravenizer().run(options)
}
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 10fe0a3b5a93..32dcbe546f3c 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -47,6 +47,9 @@ class RavenizerOptions(
/** Whether the validation failure is fatal or not. */
var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+
+ /** Whether to remove mockito and dexmaker classes. */
+ var stripMockito: SetOnce<Boolean> = SetOnce(false),
) {
companion object {
@@ -85,6 +88,9 @@ class RavenizerOptions(
"--fatal-validation" -> ret.fatalValidation.set(true)
"--no-fatal-validation" -> ret.fatalValidation.set(false)
+ "--strip-mockito" -> ret.stripMockito.set(true)
+ "--no-strip-mockito" -> ret.stripMockito.set(false)
+
else -> throw ArgumentsException("Unknown option: $arg")
}
} catch (e: SetOnce.SetMoreThanOnceException) {
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 1aa70c08c254..37a797528e13 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -100,3 +100,19 @@ fun String.shouldByBypassed(): Boolean {
// TODO -- anything else?
)
}
+
+/**
+ * Files that should be removed when "--strip-mockito" is set.
+ */
+fun String.isMockitoFile(): Boolean {
+ return this.startsWithAny(
+ "org/mockito/", // Mockito
+ "com/android/dx/", // DexMaker
+ "mockito-extensions/", // DexMaker overrides
+ )
+}
+
+fun includeUnsupportedMockito(classes: ClassNodes): Boolean {
+ return classes.findClass("com/android/dx/DexMaker") != null
+ || classes.findClass("org/mockito/Matchers") != null
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 6b6b39df24d7..a77ba624a68f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -47,7 +47,6 @@ import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.View;
@@ -1637,9 +1636,10 @@ public class FullScreenMagnificationController implements
* <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
* We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
* will be no obvious magnification effect.
+ * Only the value of the default display is persisted in user's settings.
*/
public void persistScale(int displayId) {
- final float scale = getScale(Display.DEFAULT_DISPLAY);
+ final float scale = getScale(displayId);
if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
diff --git a/services/appfunctions/TEST_MAPPING b/services/appfunctions/TEST_MAPPING
index 91cfa064d9fc..851d754af943 100644
--- a/services/appfunctions/TEST_MAPPING
+++ b/services/appfunctions/TEST_MAPPING
@@ -2,11 +2,6 @@
"presubmit": [
{
"name": "FrameworksAppFunctionsTests"
- }
- ],
- "postsubmit": [
- {
- "name": "FrameworksAppFunctionsTests"
},
{
"name": "CtsAppFunctionTestCases"
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index c87d516d2ab4..d31ced3f2ce3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,8 +16,6 @@
package com.android.server.appfunctions;
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
-import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
@@ -205,6 +203,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
.verifyCallerCanExecuteAppFunction(
callingUid,
callingPid,
+ targetUser,
requestInternal.getCallingPackage(),
targetPackageName,
requestInternal.getClientRequest().getFunctionIdentifier())
@@ -362,26 +361,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
callingPackage,
functionIdentifier,
runtimeMetadataSearchSession));
- AppFunctionRuntimeMetadata.Builder newMetadata =
- new AppFunctionRuntimeMetadata.Builder(existingMetadata);
- switch (enabledState) {
- case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
- newMetadata.setEnabled(null);
- }
- case APP_FUNCTION_STATE_ENABLED -> {
- newMetadata.setEnabled(true);
- }
- case APP_FUNCTION_STATE_DISABLED -> {
- newMetadata.setEnabled(false);
- }
- default ->
- throw new IllegalArgumentException("Value of EnabledState is unsupported.");
- }
+ AppFunctionRuntimeMetadata newMetadata =
+ new AppFunctionRuntimeMetadata.Builder(existingMetadata)
+ .setEnabled(enabledState).build();
AppSearchBatchResult<String, Void> putDocumentBatchResult =
runtimeMetadataSearchSession
.put(
new PutDocumentsRequest.Builder()
- .addGenericDocuments(newMetadata.build())
+ .addGenericDocuments(newMetadata)
.build())
.get();
if (!putDocumentBatchResult.isSuccess()) {
@@ -437,62 +424,17 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
targetUser,
mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
cancellationSignal,
- new RunServiceCallCallback<IAppFunctionService>() {
- @Override
- public void onServiceConnected(
- @NonNull IAppFunctionService service,
- @NonNull
- ServiceUsageCompleteListener
- serviceUsageCompleteListener) {
- try {
- service.executeAppFunction(
- requestInternal.getClientRequest(),
- cancellationCallback,
- new IExecuteAppFunctionCallback.Stub() {
- @Override
- public void onResult(
- ExecuteAppFunctionResponse response) {
- safeExecuteAppFunctionCallback.onResult(
- response);
- serviceUsageCompleteListener.onCompleted();
- }
- });
- } catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
- serviceUsageCompleteListener.onCompleted();
- }
- }
-
- @Override
- public void onFailedToConnect() {
- Slog.e(TAG, "Failed to connect to service");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
- }
-
- @Override
- public void onCancelled() {
- // Do not forward the result back to the caller once it has been
- // canceled. The caller does not need a notification and should
- // proceed after initiating a cancellation.
- safeExecuteAppFunctionCallback.disable();
- }
- },
+ RunAppFunctionServiceCallback.create(
+ requestInternal,
+ cancellationCallback,
+ safeExecuteAppFunctionCallback),
callerBinder);
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
safeExecuteAppFunctionCallback.onResult(
ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
"Failed to bind the AppFunctionService.",
/* extras= */ null));
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 3592ed587ab0..5393b939b5ed 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -64,6 +64,9 @@ public interface CallerValidator {
* {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
* still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
*
+ * @param callingUid The calling uid.
+ * @param callingPid The calling pid.
+ * @param targetUser The user which the caller is requesting to execute as.
* @param callerPackageName The calling package (as previously validated).
* @param targetPackageName The package that owns the app function to execute.
* @param functionId The id of the app function to execute.
@@ -72,6 +75,7 @@ public interface CallerValidator {
AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId);
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 8b6251a59e3a..e85a70d5845a 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -93,6 +93,7 @@ class CallerValidatorImpl implements CallerValidator {
public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
+ @NonNull UserHandle targetUser,
@NonNull String callerPackageName,
@NonNull String targetPackageName,
@NonNull String functionId) {
@@ -122,7 +123,10 @@ class CallerValidatorImpl implements CallerValidator {
FutureAppSearchSession futureAppSearchSession =
new FutureAppSearchSessionImpl(
- mContext.getSystemService(AppSearchManager.class),
+ Objects.requireNonNull(
+ mContext
+ .createContextAsUser(targetUser, 0)
+ .getSystemService(AppSearchManager.class)),
THREAD_POOL_EXECUTOR,
new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
new file mode 100644
index 000000000000..7820390dd544
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.util.Slog;
+
+import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
+import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+
+
+/**
+ * A callback to forward a request to the {@link IAppFunctionService} and report back the result.
+ */
+public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+
+ private final ExecuteAppFunctionAidlRequest mRequestInternal;
+ private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
+ private final ICancellationCallback mCancellationCallback;
+
+ private RunAppFunctionServiceCallback(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ this.mRequestInternal = requestInternal;
+ this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+ this.mCancellationCallback = cancellationCallback;
+ }
+
+ /**
+ * Creates a new instance of {@link RunAppFunctionServiceCallback}.
+ *
+ * @param requestInternal a request to send to the service.
+ * @param cancellationCallback a callback to forward cancellation signal to the service.
+ * @param safeExecuteAppFunctionCallback a callback to report back the result of the operation.
+ */
+ public static RunAppFunctionServiceCallback create(
+ ExecuteAppFunctionAidlRequest requestInternal,
+ ICancellationCallback cancellationCallback,
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
+ return new RunAppFunctionServiceCallback(
+ requestInternal, cancellationCallback, safeExecuteAppFunctionCallback);
+ }
+
+ @Override
+ public void onServiceConnected(
+ @NonNull IAppFunctionService service,
+ @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener) {
+ try {
+ service.executeAppFunction(
+ mRequestInternal.getClientRequest(),
+ mCancellationCallback,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse response) {
+ mSafeExecuteAppFunctionCallback.onResult(response);
+ serviceUsageCompleteListener.onCompleted();
+ }
+ });
+ } catch (Exception e) {
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ e.getMessage(),
+ /* extras= */ null));
+ serviceUsageCompleteListener.onCompleted();
+ }
+ }
+
+ @Override
+ public void onFailedToConnect() {
+ Slog.e("AppFunctionManagerServiceImpl", "Failed to connect to service");
+ mSafeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService",
+ /* extras= */ null));
+ }
+
+ @Override
+ public void onCancelled() {
+ mSafeExecuteAppFunctionCallback.disable();
+ }
+}
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 cd2dd3a27c9a..81ae717ca959 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -42,6 +42,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -153,6 +154,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+ private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -498,6 +502,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
+ String getDeviceProfile() {
+ return mAssociationInfo == null ? null : mAssociationInfo.getDeviceProfile();
+ }
+
/** Returns the public representation of the device. */
VirtualDevice getPublicVirtualDeviceObject() {
return mPublicVirtualDeviceObject;
@@ -1294,6 +1302,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return hasCustomAudioInputSupportInternal();
}
+ @Override
+ public boolean canCreateMirrorDisplays() {
+ return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile());
+ }
+
private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 9060250d9869..2acedd56e505 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -47,6 +47,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.LongArrayQueue;
import android.util.Slog;
@@ -200,6 +201,13 @@ public class PackageWatchdog {
// aborted.
private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_NOTE = 2900;
+
private static final Object sPackageWatchdogLock = new Object();
@GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
@@ -2024,7 +2032,7 @@ public class PackageWatchdog {
} else {
int count = getCount() + 1;
setCount(count);
- EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
if (Flags.recoverabilityDetection()) {
// After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
// mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index ada19530abfb..feb5775e5aac 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -42,6 +42,7 @@ import android.provider.Settings;
import android.sysprop.CrashRecoveryProperties;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -154,6 +155,14 @@ public class RescueParty {
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
+ private static final int LOG_TAG_RESCUE_FAILURE = 2903;
+
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
@@ -523,7 +532,7 @@ public class RescueParty {
Slog.w(TAG, "Attempting rescue level " + levelToString(level));
try {
executeRescueLevelInternal(context, level, failedPackage);
- EventLogTags.writeRescueSuccess(level);
+ EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
String successMsg = "Finished rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackage)) {
successMsg += " for package " + failedPackage;
@@ -704,7 +713,7 @@ public class RescueParty {
private static void logRescueException(int level, @Nullable String failedPackageName,
Throwable t) {
final String msg = getCompleteMessage(t);
- EventLogTags.writeRescueFailure(level, msg);
+ EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
String failureMsg = "Failed rescue level " + levelToString(level);
if (!TextUtils.isEmpty(failedPackageName)) {
failureMsg += " for package " + failedPackageName;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a459ea944008..ce66dc3c76cb 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -114,6 +114,9 @@
"options": [
{
"include-filter": "android.os.storage.cts.StorageManagerTest"
+ },
+ {
+ "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
}
]
}
@@ -173,15 +176,6 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
- },
- {
- "name": "CtsOsTestCases",
- "file_patterns": ["StorageManagerService\\.java"],
- "options": [
- {
- "include-filter": "android.os.storage.cts.StorageStatsManagerTest"
- }
- ]
}
]
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f32031dec43c..7daf15821047 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
@@ -138,7 +139,7 @@ final class UiModeManagerService extends SystemService {
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private final NightMode mNightMode = new NightMode(){
+ private final IntProperty mNightMode = new IntProperty(){
private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
@Override
@@ -192,7 +193,22 @@ final class UiModeManagerService extends SystemService {
// flag set by resource, whether to night mode change for normal all or not.
private boolean mNightModeLocked = false;
- int mCurUiMode = 0;
+ private final IntProperty mCurUiMode = new IntProperty(){
+ private int mCurrentModeTypeValue = 0;
+
+ @Override
+ public int get() {
+ return mCurrentModeTypeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mCurrentModeTypeValue = mode;
+ if (enableCurrentModeTypeBinderCache()) {
+ UiModeManager.invalidateCurrentModeTypeCache();
+ }
+ }
+ };
private int mSetUiMode = 0;
private boolean mHoldingConfiguration = false;
private int mCurrentUser;
@@ -810,7 +826,7 @@ final class UiModeManagerService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ return mCurUiMode.get() & Configuration.UI_MODE_TYPE_MASK;
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1492,7 +1508,7 @@ final class UiModeManagerService extends SystemService {
pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
- pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+ pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode.get()));
pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
@@ -1745,7 +1761,7 @@ final class UiModeManagerService extends SystemService {
+ "; uiMode=" + uiMode);
}
- mCurUiMode = uiMode;
+ mCurUiMode.set(uiMode);
if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
mConfiguration.uiMode = uiMode;
}
@@ -1892,7 +1908,7 @@ final class UiModeManagerService extends SystemService {
boolean keepScreenOn = mCharging &&
((mCarModeEnabled && mCarModeKeepsScreenOn &&
(mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
- (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+ (mCurUiMode.get() == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
if (keepScreenOn != mWakeLock.isHeld()) {
if (keepScreenOn) {
mWakeLock.acquire();
@@ -2319,11 +2335,12 @@ final class UiModeManagerService extends SystemService {
}
/**
- * Interface to contain the value for system night mode. We make the night mode accessible
- * through this class to ensure that the reassignment of this value invalidates the cache.
+ * Interface to contain the value for an integral property. We make the property
+ * accessible through this class to ensure that the reassignment of this value invalidates the
+ * cache.
*/
- private interface NightMode {
+ private interface IntProperty {
int get();
- void set(int mode);
+ void set(int value);
}
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 71b64567d062..aca6d0b0b967 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -1005,7 +1005,8 @@ public final class AppStartInfoTracker {
case (int) AppsStartInfoProto.Package.USERS:
AppStartInfoContainer container =
new AppStartInfoContainer(mAppStartInfoHistoryListSize);
- int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS);
+ int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS,
+ pkgName);
synchronized (mLock) {
mData.put(pkgName, uid, container);
}
@@ -1403,7 +1404,7 @@ public final class AppStartInfoTracker {
proto.end(token);
}
- int readFromProto(ProtoInputStream proto, long fieldId)
+ int readFromProto(ProtoInputStream proto, long fieldId, String packageName)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
long token = proto.start(fieldId);
for (int next = proto.nextField();
@@ -1418,6 +1419,7 @@ public final class AppStartInfoTracker {
// have a create time.
ApplicationStartInfo info = new ApplicationStartInfo(0);
info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+ info.setPackageName(packageName);
mInfos.add(info);
break;
case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 78a0a117fe6f..796de1982fe5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -458,7 +458,13 @@ public class OomAdjuster {
}
void setThreadPriority(int tid, int priority) {
- Process.setThreadPriority(tid, priority);
+ if (Flags.resetOnForkEnabled()) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK,
+ priority);
+ } else {
+ Process.setThreadPriority(tid, priority);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 4f6da3baca12..7873d34e9047 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -198,13 +198,10 @@ flag {
flag {
name: "logcat_longer_timeout"
- namespace: "backstage_power"
+ namespace: "stability"
description: "Wait longer during the logcat gathering operation"
bug: "292533246"
is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
}
flag {
@@ -214,3 +211,10 @@ flag {
description: "Defer submitting display events to frozen processes."
bug: "326315985"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "system_performance"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 875041540f40..0475b94c784a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -384,11 +384,12 @@ public class AudioDeviceBroker {
/**
* Indicates if a Bluetooth SCO activation request owner is controlling
* the SCO audio state itself or not.
- * @param uid the UI of the SOC request owner app
+ * @param uid the UID of the SOC request owner app
* @return true if we should control SCO audio state, false otherwise
*/
private boolean shouldStartScoForUid(int uid) {
- return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+ return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID));
}
@GuardedBy("mDeviceStateLock")
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5fd12c29d2f8..09de89445122 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -385,11 +385,6 @@ public class AudioDeviceInventory {
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
- if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
- }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 561030e77e61..c37d47149ef5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1583,8 +1583,11 @@ public class AudioService extends IAudioService.Stub
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
- stream);
+ boolean enabled = true;
+ if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ enabled = mAvrcpAbsVolSupported;
+ }
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", enabled, stream);
});
}
}
@@ -4881,7 +4884,7 @@ public class AudioService extends IAudioService.Stub
if (absDev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
enabled = mAvrcpAbsVolSupported;
}
- if (stream != streamType) {
+ if (stream != streamType || !enabled) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/"",
enabled, streamType);
}
@@ -10097,9 +10100,6 @@ public class AudioService extends IAudioService.Stub
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
- // the device inventory can only be synchronized after the
- // spatializer has been initialized
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
mAudioEventWakeLock.release();
break;
@@ -10383,10 +10383,10 @@ public class AudioService extends IAudioService.Stub
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
- mAvrcpAbsVolSupported = support;
- if (absVolumeIndexFix()) {
- int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mCachedAbsVolDrivingStreamsLock) {
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mAvrcpAbsVolSupported = support;
+ if (absVolumeIndexFix()) {
+ int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
if (!mAvrcpAbsVolSupported) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
@@ -12499,6 +12499,12 @@ public class AudioService extends IAudioService.Stub
pw.println("\nLoudness alignment:");
mLoudnessCodecHelper.dump(pw);
+ pw.println("\nAbsolute voume devices:");
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.forEach((dev, stream) -> pw.println(
+ "Device type: 0x" + Integer.toHexString(dev) + ", driving stream " + stream));
+ }
+
mAudioSystem.dump(pw);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index abfbddc18e24..3afecf1d8bbf 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -879,6 +879,14 @@ public final class AuthSession implements IBinder.DeathRecipient {
);
break;
+ case BiometricPrompt.DISMISSED_REASON_ERROR_NO_WM:
+ mClientReceiver.onError(
+ getEligibleModalities(),
+ BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */
+ );
+ break;
+
default:
Slog.w(TAG, "Unhandled reason: " + reason);
break;
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index de7bce78587b..87341365355b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -253,9 +253,15 @@ public class Utils {
// Check if any of the non-biometric and non-credential bits are set. If so, this is
// invalid.
- final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
- | Authenticators.BIOMETRIC_MIN_STRENGTH
- | Authenticators.MANDATORY_BIOMETRICS);
+ final int testBits;
+ if (Flags.mandatoryBiometrics()) {
+ testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH
+ | Authenticators.MANDATORY_BIOMETRICS);
+ } else {
+ testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH);
+ }
if ((authenticators & testBits) != 0) {
Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+ " Authenticators: " + authenticators);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index af9c9accb635..8d96ba93530b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -377,6 +377,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </map>
* </luxToBrightnessMapping>
+ * <idleStylusTimeoutMillis>10000</idleStylusTimeoutMillis>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -708,6 +709,10 @@ public class DisplayDeviceConfig {
private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+ // The default value to 0 which will signify that the stylus usage immediately stopped
+ // after it was started. This will make the system behave as if the stylus was never used
+ private static final int DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS = 0;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -754,6 +759,9 @@ public class DisplayDeviceConfig {
@Nullable
private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
+ private int mIdleStylusTimeoutMillis =
+ DEFAULT_IDLE_STYLUS_TIMEOUT_MILLIS;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -1730,6 +1738,7 @@ public class DisplayDeviceConfig {
+ ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ + ", mIdleStylusTimeoutMillis= " + mIdleStylusTimeoutMillis
+ "\n"
+ "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
@@ -2389,10 +2398,19 @@ public class DisplayDeviceConfig {
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
autoBrightness, getBacklightToBrightnessSpline());
+ loadIdleStylusTimeoutMillis(autoBrightness);
loadEnableAutoBrightness(autoBrightness);
}
/**
+ * Gets the timeout post the stylus usage after which the automatic brightness will be enabled
+ * again
+ */
+ public int getIdleStylusTimeoutMillis() {
+ return mIdleStylusTimeoutMillis;
+ }
+
+ /**
* Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
* the value from the display config, and if not present, falls back to config.xml.
*/
@@ -2923,6 +2941,16 @@ public class DisplayDeviceConfig {
return levels;
}
+ private void loadIdleStylusTimeoutMillis(AutoBrightness autoBrightness) {
+ if (autoBrightness == null) {
+ return;
+ }
+ BigInteger idleStylusTimeoutMillis = autoBrightness.getIdleStylusTimeoutMillis();
+ if (idleStylusTimeoutMillis != null) {
+ mIdleStylusTimeoutMillis = idleStylusTimeoutMillis.intValue();
+ }
+ }
+
private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
// mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
// config.xml values if the autobrightness tag is not defined in the ddc file.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4152ec9e26e3..bb503aaab471 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1661,33 +1661,49 @@ public final class DisplayManagerService extends SystemService {
return false;
}
+ private boolean hasVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_VIDEO_OUTPUT, func)
+ || hasSecureVideoOutputPermission(func);
+ }
+
+ private boolean hasSecureVideoOutputPermission(String func) {
+ return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, func);
+ }
+
+ private boolean canCreateMirrorDisplays(IVirtualDevice virtualDevice) {
+ if (virtualDevice == null) {
+ return false;
+ }
+ try {
+ return virtualDevice.canCreateMirrorDisplays();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query virtual device for permissions", e);
+ return false;
+ }
+ }
+
private boolean canProjectVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
}
- if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) {
- return true;
+ try {
+ return projection.canProjectVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return canProjectSecureVideo(projection);
}
private boolean canProjectSecureVideo(IMediaProjection projection) {
- if (projection != null) {
- try {
- if (projection.canProjectSecureVideo()) {
- return true;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to query projection service for permissions", e);
- }
+ if (projection == null) {
+ return false;
+ }
+ try {
+ return projection.canProjectSecureVideo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ return false;
}
- return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()");
}
private boolean checkCallingPermission(String permission, String func) {
@@ -1793,7 +1809,8 @@ public final class DisplayManagerService extends SystemService {
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
// Only a valid media projection or a virtual device can create a mirror virtual
// display.
- if (!canProjectVideo(projection) && virtualDevice == null) {
+ if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice)
+ && !hasVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
@@ -1803,7 +1820,8 @@ public final class DisplayManagerService extends SystemService {
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
- if (!canProjectSecureVideo(projection)) {
+ if (!canProjectSecureVideo(projection)
+ && !hasSecureVideoOutputPermission("createVirtualDisplayInternal")) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
+ "secure virtual display.");
@@ -2093,16 +2111,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
- synchronized (mSyncRoot) {
- if (mVirtualDisplayAdapter == null) {
- return;
- }
-
- mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
- }
- }
-
private void setVirtualDisplayRotationInternal(IBinder appToken,
@Surface.Rotation int rotation) {
int displayId;
@@ -4615,16 +4623,6 @@ public final class DisplayManagerService extends SystemService {
}
@Override // Binder call
- public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
- final long token = Binder.clearCallingIdentity();
- try {
- setVirtualDisplayStateInternal(callback.asBinder(), isOn);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
public void setVirtualDisplayRotation(IVirtualDisplayCallback callback,
@Surface.Rotation int rotation) {
if (!android.companion.virtualdevice.flags.Flags.virtualDisplayRotationApi()) {
@@ -5648,6 +5646,21 @@ public final class DisplayManagerService extends SystemService {
public void onPresentation(int displayId, boolean isShown) {
mExternalDisplayPolicy.onPresentation(displayId, isShown);
}
+
+ @Override
+ public void stylusGestureStarted(long eventTime) {
+ if (mFlags.isBlockAutobrightnessChangesOnStylusUsage()) {
+ DisplayPowerController displayPowerController;
+ synchronized (mSyncRoot) {
+ displayPowerController = mDisplayPowerControllers.get(
+ Display.DEFAULT_DISPLAY);
+ }
+ // We assume that the stylus is being used on the default display. This should
+ // be changed to the displayId on which it is being used once we start getting this
+ // information from the input manager service
+ displayPowerController.stylusGestureStarted(eventTime);
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 03fec0115613..8f07bb37e2ff 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -167,12 +167,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
+ private static final int MSG_SET_STYLUS_BEING_USED = 19;
+ private static final int MSG_SET_STYLUS_USE_ENDED = 20;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
private static final int RAMP_STATE_SKIP_INITIAL = 1;
@@ -191,6 +190,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
+
+ private static final int STYLUS_USAGE_DEBOUNCE_TIME = 1000;
+ private static final int NANO_SECONDS_TO_MILLI_SECONDS_RATIO = 1_000_000;
+
private static final int[] BRIGHTNESS_RANGE_INDEX = {
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
@@ -498,6 +501,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@GuardedBy("mLock")
private int mPendingOverrideDozeScreenStateLocked;
+ private long mLastStylusUsageEventTime = -1;
+
+ // The time of inactivity after which the stylus can be assumed to be no longer in use.
+ private long mIdleStylusTimeoutMillisConfig = 0;
+
/**
* Creates the display power controller.
*/
@@ -518,6 +526,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mSensorManager = sensorManager;
mHandler = new DisplayControllerHandler(handler.getLooper());
mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mIsEnabled = logicalDisplay.isEnabledLocked();
mIsInTransition = logicalDisplay.isInTransitionLocked();
mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL;
@@ -893,6 +902,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mPhysicalDisplayName = displayName;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mIdleStylusTimeoutMillisConfig = mDisplayDeviceConfig.getIdleStylusTimeoutMillis();
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -2971,6 +2981,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
}
+ public void stylusGestureStarted(long eventTimeNanoSeconds) {
+ long eventTimeMs = eventTimeNanoSeconds / NANO_SECONDS_TO_MILLI_SECONDS_RATIO;
+ if (mLastStylusUsageEventTime == -1
+ || eventTimeMs > mLastStylusUsageEventTime + STYLUS_USAGE_DEBOUNCE_TIME) {
+ synchronized (mLock) {
+ // Add a message to notify the stylus usage has started
+ mHandler.sendEmptyMessageAtTime(MSG_SET_STYLUS_BEING_USED, mClock.uptimeMillis());
+ }
+ mLastStylusUsageEventTime = eventTimeMs;
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -3087,6 +3109,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
updatePowerState();
}
break;
+ case MSG_SET_STYLUS_BEING_USED:
+ // Remove any MSG_SET_STYLUS_USE_ENDED message from the handler queue and
+ // post a delayed MSG_SET_STYLUS_USE_ENDED message to delay the stylus
+ // usage ended event processing
+ mHandler.removeMessages(MSG_SET_STYLUS_USE_ENDED);
+ Message message = mHandler.obtainMessage(MSG_SET_STYLUS_USE_ENDED);
+ mHandler.sendMessageAtTime(message,
+ mClock.uptimeMillis() + mIdleStylusTimeoutMillisConfig);
+ mDisplayBrightnessController.setStylusBeingUsed(true);
+ break;
+ case MSG_SET_STYLUS_USE_ENDED:
+ mDisplayBrightnessController.setStylusBeingUsed(false);
+ updatePowerState();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bcb600d0f91c..06a910396d6c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -355,8 +355,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public SparseArray<int[]> getDisplayIdsByGroupIdLocked() {
SparseArray<int[]> displayIdsByGroupIds = new SparseArray<>();
for (int i = 0; i < mDisplayGroups.size(); i++) {
- int groupId = mDisplayGroups.get(i).getGroupId();
- displayIdsByGroupIds.put(groupId, getDisplayIdsForGroupLocked(groupId));
+ final int displayGroupId = mDisplayGroups.keyAt(i);
+ displayIdsByGroupIds.put(displayGroupId, getDisplayIdsForGroupLocked(displayGroupId));
}
return displayIdsByGroupIds;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9b02f4b71ebe..e77c5ec50f8b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -207,13 +207,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
return device;
}
- void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
- VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
- if (device != null) {
- device.setDisplayState(isOn);
- }
- }
-
DisplayDevice getDisplayDevice(IBinder appToken) {
return mVirtualDisplayDevices.get(appToken);
}
@@ -273,7 +266,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private boolean mStopped;
private int mPendingChanges;
private Display.Mode mMode;
- private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
private DisplayCutout mDisplayCutout;
@@ -299,9 +291,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayState = Display.STATE_ON;
mPendingChanges |= PENDING_SURFACE_CHANGE;
- mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
}
@@ -394,6 +385,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
if (state == Display.STATE_OFF) {
mCallback.dispatchDisplayPaused();
} else {
@@ -416,12 +409,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
public void setSurfaceLocked(Surface surface) {
if (!mStopped && mSurface != surface) {
- if ((mSurface != null) != (surface != null)) {
+ if (mDisplayState == Display.STATE_ON
+ && ((mSurface == null) != (surface == null))) {
+ mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
sendTraversalRequestLocked();
mSurface = surface;
- mInfo = null;
mPendingChanges |= PENDING_SURFACE_CHANGE;
}
}
@@ -439,14 +433,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
}
- void setDisplayState(boolean isOn) {
- if (mIsDisplayOn != isOn) {
- mIsDisplayOn = isOn;
- mInfo = null;
- sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
- }
- }
-
public void stopLocked() {
Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
@@ -567,7 +553,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
- mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+ if (mSurface == null) {
+ mInfo.state = Display.STATE_OFF;
+ } else {
+ mInfo.state = mDisplayState;
+ }
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 72a91d59c911..71fdaf3f85b6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -501,6 +501,13 @@ public final class DisplayBrightnessController {
return true;
}
+ /**
+ * Notifies if the stylus is currently being used or not.
+ */
+ public void setStylusBeingUsed(boolean isEnabled) {
+ // Todo(b/369977976) - Disable the auto-brightness strategy
+ }
+
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
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 df66893a2f35..5284d1c423f6 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -203,6 +203,10 @@ public class DisplayManagerFlags {
Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER,
Flags::normalBrightnessForDozeParameter
);
+ private final FlagState mBlockAutobrightnessChangesOnStylusUsage = new FlagState(
+ Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
+ Flags::blockAutobrightnessChangesOnStylusUsage
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -436,6 +440,13 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if autobrightness is to be blocked when stylus is being used
+ */
+ public boolean isBlockAutobrightnessChangesOnStylusUsage() {
+ return mBlockAutobrightnessChangesOnStylusUsage.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -479,6 +490,7 @@ public class DisplayManagerFlags {
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
+ pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
}
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 e3ebe5bcd9ed..252ed09fd125 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
@@ -364,4 +364,12 @@ flag {
description: "Flag to enable battery stats for all displays."
bug: "366112793"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "block_autobrightness_changes_on_stylus_usage"
+ namespace: "display_manager"
+ description: "Block the usage of ALS to control the display brightness when stylus is being used"
+ bug: "352411468"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f04557665477..8acf583e0765 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2694,6 +2694,9 @@ public class InputManagerService extends IInputManager.Stub
@SuppressWarnings("unused")
private void notifyStylusGestureStarted(int deviceId, long eventTime) {
mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ if (mDisplayManagerInternal != null) {
+ mDisplayManagerInternal.stylusGestureStarted(eventTime);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e1b8e9f559ed..8b06dadbaeeb 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1653,9 +1653,11 @@ class MediaRouter2ServiceImpl {
manager));
}
+ List<MediaRoute2Info> routes =
+ userRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters.values().stream()
+ .toList();
userRecord.mHandler.sendMessage(
- obtainMessage(
- UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
+ obtainMessage(ManagerRecord::notifyRoutesUpdated, managerRecord, routes));
}
@GuardedBy("mLock")
@@ -2433,6 +2435,51 @@ class MediaRouter2ServiceImpl {
}
}
+ /**
+ * Notifies the corresponding manager of the availability of the given routes.
+ *
+ * @param routes The routes available to the manager that corresponds to this record.
+ */
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ try {
+ mManager.notifyRoutesUpdated(routes);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify routes. Manager probably died.", ex);
+ }
+ }
+
+ /**
+ * Notifies the corresponding manager of an update in the given session.
+ *
+ * @param sessionInfo The updated session info.
+ */
+ public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ try {
+ mManager.notifySessionUpdated(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "notifySessionUpdatedToManagers: Failed to notify. Manager probably died.",
+ ex);
+ }
+ }
+
+ /**
+ * Notifies the corresponding manager that the given session has been released.
+ *
+ * @param sessionInfo The released session info.
+ */
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+ try {
+ mManager.notifySessionReleased(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "notifySessionReleasedToManagers: Failed to notify. Manager probably died.",
+ ex);
+ }
+ }
+
private void updateScanningState(@ScanningState int scanningState) {
if (mScanningState == scanningState) {
return;
@@ -2761,18 +2808,20 @@ class MediaRouter2ServiceImpl {
getRouterRecords(/* hasSystemRoutingPermission= */ true);
List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
getRouterRecords(/* hasSystemRoutingPermission= */ false);
- List<IMediaRouter2Manager> managers = getManagers();
+ List<ManagerRecord> managers = getManagerRecords();
// Managers receive all provider updates with all routes.
- notifyRoutesUpdatedToManagers(
- managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+ List<MediaRoute2Info> routesForPrivilegedRouters =
+ mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList();
+ for (ManagerRecord manager : managers) {
+ manager.notifyRoutesUpdated(routesForPrivilegedRouters);
+ }
// Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
// {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
// with all routes.
notifyRoutesUpdatedToRouterRecords(
- routerRecordsWithSystemRoutingPermission,
- new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+ routerRecordsWithSystemRoutingPermission, routesForPrivilegedRouters);
if (!isSystemProvider) {
// Regular routers receive updates from all non-system providers with all non-system
@@ -3068,8 +3117,10 @@ class MediaRouter2ServiceImpl {
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
- List<IMediaRouter2Manager> managers = getManagers();
- notifySessionUpdatedToManagers(managers, sessionInfo);
+ List<ManagerRecord> managers = getManagerRecords();
+ for (ManagerRecord manager : managers) {
+ manager.notifySessionUpdated(sessionInfo);
+ }
// For system provider, notify all routers.
if (provider == mSystemProvider) {
@@ -3093,8 +3144,10 @@ class MediaRouter2ServiceImpl {
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
- List<IMediaRouter2Manager> managers = getManagers();
- notifySessionReleasedToManagers(managers, sessionInfo);
+ List<ManagerRecord> managers = getManagerRecords();
+ for (ManagerRecord manager : managers) {
+ manager.notifySessionReleased(sessionInfo);
+ }
RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
if (routerRecord == null) {
@@ -3169,20 +3222,6 @@ class MediaRouter2ServiceImpl {
return true;
}
- private List<IMediaRouter2Manager> getManagers() {
- final List<IMediaRouter2Manager> managers = new ArrayList<>();
- MediaRouter2ServiceImpl service = mServiceRef.get();
- if (service == null) {
- return managers;
- }
- synchronized (service.mLock) {
- for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
- managers.add(managerRecord.mManager);
- }
- }
- return managers;
- }
-
private List<RouterRecord> getRouterRecords() {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
@@ -3269,37 +3308,6 @@ class MediaRouter2ServiceImpl {
}
}
- /**
- * Notifies {@code manager} with all known routes. This only happens once after {@code
- * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
- * registerManager()}.
- *
- * @param manager {@link IMediaRouter2Manager} to be notified.
- */
- private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
- if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
- return;
- }
- try {
- manager.notifyRoutesUpdated(
- new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
- }
- }
-
- private void notifyRoutesUpdatedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesUpdated(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
- }
- }
- }
-
private void notifySessionCreatedToManagers(long managerRequestId,
@NonNull RoutingSessionInfo session) {
int requesterId = toRequesterId(managerRequestId);
@@ -3317,32 +3325,6 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifySessionUpdatedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifySessionUpdated(sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "notifySessionUpdatedToManagers: "
- + "Failed to notify. Manager probably died.", ex);
- }
- }
- }
-
- private void notifySessionReleasedToManagers(
- @NonNull List<IMediaRouter2Manager> managers,
- @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifySessionReleased(sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "notifySessionReleasedToManagers: "
- + "Failed to notify. Manager probably died.", ex);
- }
- }
- }
-
private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
@NonNull IMediaRouter2Manager manager) {
try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0a9109b3e98c..d752429e64f7 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
@@ -48,9 +47,7 @@ import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetadata;
-import android.media.MediaRouter2Manager;
import android.media.Rating;
-import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -186,7 +183,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private final MediaSessionService mService;
private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
- private final boolean mVolumeAdjustmentForRemoteGroupSessions;
private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
@@ -311,8 +307,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
- mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
@@ -659,49 +653,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
return false;
}
- if (mVolumeAdjustmentForRemoteGroupSessions) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Volume adjustment for remote group sessions allowed so MediaSessionRecord"
- + " can handle volume key");
- }
- return true;
- }
- // See b/228021646 for details.
- MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
- List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
- boolean foundNonSystemSession = false;
- boolean remoteSessionAllowVolumeAdjustment = true;
- if (DEBUG) {
- Slog.d(
- TAG,
- "Found "
- + sessions.size()
- + " routing sessions for package name "
- + mPackageName);
- }
- for (RoutingSessionInfo session : sessions) {
- if (DEBUG) {
- Slog.d(TAG, "Found routingSessionInfo: " + session);
- }
- if (!session.isSystemSession()) {
- foundNonSystemSession = true;
- if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
- remoteSessionAllowVolumeAdjustment = false;
- }
- }
- }
- if (!foundNonSystemSession) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Package " + mPackageName
- + " has a remote media session but no associated routing session");
- }
- }
-
- return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 47f579db604f..e7e519ede768 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -42,6 +42,8 @@ import android.app.AppOpsManager;
import android.app.IProcessObserver;
import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -94,7 +96,7 @@ import java.util.Objects;
/**
* Manages MediaProjection sessions.
- *
+ * <p>
* The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
* as well as the capabilities they grant. Any service using MediaProjection tokens as permission
* grants <b>must</b> validate the token before use by calling {@link
@@ -137,6 +139,7 @@ public final class MediaProjectionManagerService extends SystemService
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
private final KeyguardManager mKeyguardManager;
+ private final RoleManager mRoleManager;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -173,6 +176,7 @@ public final class MediaProjectionManagerService extends SystemService
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mKeyguardManager.addKeyguardLockedStateListener(
mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
+ mRoleManager = mContext.getSystemService(RoleManager.class);
Watchdog.getInstance().addMonitor(this);
}
@@ -182,6 +186,7 @@ public final class MediaProjectionManagerService extends SystemService
* - be one of the bugreport allowlisted packages, or
* - hold the OP_PROJECT_MEDIA AppOp.
*/
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean canCaptureKeyguard() {
if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
return true;
@@ -193,6 +198,9 @@ public final class MediaProjectionManagerService extends SystemService
if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
mProjectionGrant.packageName)
== PackageManager.PERMISSION_GRANTED) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
+ + "permission");
return true;
}
if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
@@ -200,6 +208,13 @@ public final class MediaProjectionManagerService extends SystemService
"recording lockscreen")) {
// Some tools use media projection by granting the OP_PROJECT_MEDIA app
// op via a shell command. Those tools can be granted keyguard capture
+ Slog.v(TAG,
+ "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
+ return true;
+ }
+ if (isProjectionAppHoldingAppStreamingRoleLocked()) {
+ Slog.v(TAG,
+ "Allowing keyguard capture for package holding app streaming role.");
return true;
}
return SystemConfig.getInstance().getBugreportWhitelistedPackages()
@@ -698,6 +713,20 @@ public final class MediaProjectionManagerService extends SystemService
}
}
+ /**
+ * Application holding the app streaming role
+ * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
+ * lockscreen.
+ *
+ * @return true if the is held by the recording application.
+ */
+ @GuardedBy("mLock")
+ private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
+ return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ mContext.getUser())
+ .contains(mProjectionGrant.packageName);
+ }
+
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 9d30c565bd96..9b9be4cd8f3f 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -832,8 +832,9 @@ public class GroupHelper {
FullyQualifiedGroupKey newGroup) { }
/**
- * Called when a notification channel is updated, so that this helper can adjust
- * the aggregate groups by moving children if their section has changed.
+ * Called when a notification channel is updated (channel attributes have changed),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
* see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
* @param userId the userId of the channel
* @param pkgName the channel's package
@@ -853,24 +854,48 @@ public class GroupHelper {
}
}
- // The list of notification operations required after the channel update
- final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ regroupNotifications(userId, pkgName, notificationsToCheck);
+ }
+ }
+
+ /**
+ * Called when an individuial notification's channel is updated (moved to a new channel),
+ * so that this helper can adjust the aggregate groups by moving children
+ * if their section has changed.
+ * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
+ * @param record the notification which had its channel updated
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onChannelUpdated(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ notificationsToCheck.put(record.getKey(), record);
+ regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+ notificationsToCheck);
+ }
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private void regroupNotifications(int userId, String pkgName,
+ ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ // The list of notification operations required after the channel update
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
- // Check any already auto-grouped notifications that may need to be re-grouped
- // after the channel update
- notificationsToMove.addAll(
- getAutogroupedNotificationsMoveOps(userId, pkgName,
- notificationsToCheck));
+ // Check any already auto-grouped notifications that may need to be re-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getAutogroupedNotificationsMoveOps(userId, pkgName,
+ notificationsToCheck));
- // Check any ungrouped notifications that may need to be auto-grouped
- // after the channel update
- notificationsToMove.addAll(
- getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+ // Check any ungrouped notifications that may need to be auto-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
- // Batch move to new section
- if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
- }
+ // Batch move to new section
+ if (!notificationsToMove.isEmpty()) {
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
}
}
@@ -1435,6 +1460,10 @@ public class GroupHelper {
return false;
}
+ if (record.getSbn().getNotification().isMediaNotification()) {
+ return false;
+ }
+
return true;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 97bbc2338f47..2dd4f8392fdf 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Flags.notificationForceGrouping;
+
import android.content.Context;
import android.util.Slog;
@@ -24,6 +27,7 @@ import android.util.Slog;
public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
private static final String TAG = "AdjustmentExtractor";
private static final boolean DBG = false;
+ private GroupHelper mGroupHelper;
public void initialize(Context ctx, NotificationUsageStats usageStats) {
@@ -35,8 +39,27 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
if (DBG) Slog.d(TAG, "skipping empty notification");
return null;
}
+
+ final boolean hasAdjustedClassification = record.hasAdjustment(KEY_TYPE);
record.applyAdjustments();
+ if (notificationForceGrouping()
+ && android.service.notification.Flags.notificationClassification()) {
+ // Classification adjustments trigger regrouping
+ if (mGroupHelper != null && hasAdjustedClassification) {
+ return new RankingReconsideration(record.getKey(), 0) {
+ @Override
+ public void work() {
+ }
+
+ @Override
+ public void applyChangesLocked(NotificationRecord record) {
+ mGroupHelper.onChannelUpdated(record);
+ }
+ };
+ }
+ }
+
return null;
}
@@ -49,4 +72,9 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
public void setZenHelper(ZenModeHelper helper) {
}
+
+ @Override
+ public void setGroupHelper(GroupHelper groupHelper) {
+ mGroupHelper = groupHelper;
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 06f419a785f9..ea4a6db7a1e8 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.Flags.sortSectionByTime;
+import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -42,6 +43,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -1641,7 +1643,7 @@ public final class NotificationAttentionHelper {
}
// recent conversation
- if (record.isConversation()
+ if ((record.isConversation() || isConversationMessage(record))
&& record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
@@ -1656,6 +1658,21 @@ public final class NotificationAttentionHelper {
return false;
}
+
+ // Relaxed signals for conversations messages
+ private boolean isConversationMessage(final NotificationRecord record) {
+ if (!CATEGORY_MESSAGE.equals(record.getSbn().getNotification().category)) {
+ return false;
+ }
+ if (record.getChannel().isDemoted()) {
+ return false;
+ }
+ final ShortcutInfo shortcut = record.getShortcutInfo();
+ if (shortcut == null) {
+ return false;
+ }
+ return true;
+ }
}
//====================== Observers =============================
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 655f2e4596aa..99e66a222370 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -519,6 +519,7 @@ public class NotificationManagerService extends SystemService {
private static final long DELAY_FORCE_REGROUP_TIME = 3000;
+
private static final String ACTION_NOTIFICATION_TIMEOUT =
NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
private static final int REQUEST_CODE_TIMEOUT = 1;
@@ -2583,7 +2584,7 @@ public class NotificationManagerService extends SystemService {
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
- mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
+ mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat, groupHelper);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
@@ -4117,6 +4118,34 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void setAdjustmentTypeSupportedState(INotificationListener token,
+ @Adjustment.Keys String key, boolean supported) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mAssistants.checkServiceTokenLocked(token);
+ if (key == null) {
+ return;
+ }
+ mAssistants.setAdjustmentTypeSupportedState(info, key, supported);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public @NonNull List<String> getUnsupportedAdjustmentTypes() {
+ checkCallerIsSystemOrSystemUiOrShell();
+ synchronized (mNotificationLock) {
+ return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
+ UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+ }
+ }
+
+ @Override
@FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
public boolean appCanBePromoted(String pkg, int uid) {
checkCallerIsSystemOrSystemUiOrShell();
@@ -6843,22 +6872,9 @@ public class NotificationManagerService extends SystemService {
}
if (android.service.notification.Flags.notificationClassification()
&& adjustments.containsKey(KEY_TYPE)) {
- NotificationChannel newChannel = null;
- int type = adjustments.getInt(KEY_TYPE);
- if (TYPE_NEWS == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
- } else if (TYPE_PROMOTION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
- } else if (TYPE_SOCIAL_MEDIA == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
- } else if (TYPE_CONTENT_RECOMMENDATION == type) {
- newChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
- }
- if (newChannel == null) {
+ final NotificationChannel newChannel = getClassificationChannelLocked(r,
+ adjustments);
+ if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
adjustments.remove(KEY_TYPE);
} else {
// swap app provided type with the real thing
@@ -6874,6 +6890,27 @@ public class NotificationManagerService extends SystemService {
}
}
+ @GuardedBy("mNotificationLock")
+ @Nullable
+ private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
+ Bundle adjustments) {
+ int type = adjustments.getInt(KEY_TYPE);
+ if (TYPE_NEWS == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
+ } else if (TYPE_PROMOTION == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
+ } else if (TYPE_SOCIAL_MEDIA == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
+ } else if (TYPE_CONTENT_RECOMMENDATION == type) {
+ return mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+ }
+ return null;
+ }
+
@SuppressWarnings("GuardedBy")
@GuardedBy("mNotificationLock")
void addAutogroupKeyLocked(String key, String groupName, boolean requestSort) {
@@ -7435,7 +7472,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecord r = findNotificationLocked(pkg, null, notificationId, userId);
if (r != null) {
if (DBG) {
- final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
+ final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
Slog.d(TAG, "Remove " + type + " flag not allow. "
+ "Cancel " + type + " notification");
}
@@ -7452,7 +7489,11 @@ public class NotificationManagerService extends SystemService {
// strip flag from all enqueued notifications. listeners will be informed
// in post runnable.
StatusBarNotification sbn = r.getSbn();
- sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ if (notificationForceGrouping()) {
+ sbn.getNotification().flags = (r.getFlags() & ~flag);
+ } else {
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ }
}
}
@@ -7461,7 +7502,11 @@ public class NotificationManagerService extends SystemService {
if (r != null) {
// if posted notification exists, strip its flag and tell listeners
StatusBarNotification sbn = r.getSbn();
- sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ if (notificationForceGrouping()) {
+ sbn.getNotification().flags = (r.getFlags() & ~flag);
+ } else {
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ }
mRankingHelper.sort(mNotificationList);
mListeners.notifyPostedLocked(r, r);
}
@@ -9459,6 +9504,28 @@ public class NotificationManagerService extends SystemService {
}
/**
+ * Check if the notification was a summary that has been auto-grouped
+ * @param r the current notification record
+ * @param old the previous notification record
+ * @return true if the notification record was a summary that was auto-grouped
+ */
+ @GuardedBy("mNotificationLock")
+ private boolean wasSummaryAutogrouped(NotificationRecord r, NotificationRecord old) {
+ boolean wasAutogrouped = false;
+ if (old != null) {
+ boolean wasSummary = (old.mOriginalFlags & FLAG_GROUP_SUMMARY) != 0;
+ boolean wasForcedGrouped = (old.getFlags() & FLAG_GROUP_SUMMARY) == 0
+ && old.getSbn().getOverrideGroupKey() != null;
+ boolean isNotAutogroupSummary = (r.getFlags() & FLAG_AUTOGROUP_SUMMARY) == 0
+ && (r.getFlags() & FLAG_GROUP_SUMMARY) != 0;
+ if ((wasSummary && wasForcedGrouped) || (wasForcedGrouped && isNotAutogroupSummary)) {
+ wasAutogrouped = true;
+ }
+ }
+ return wasAutogrouped;
+ }
+
+ /**
* Ensures that grouped notification receive their special treatment.
*
* <p>Cancels group children if the new notification causes a group to lose
@@ -9478,14 +9545,9 @@ public class NotificationManagerService extends SystemService {
}
if (notificationForceGrouping()) {
- if (old != null) {
- // If this is an update to a summary that was forced grouped => remove summary flag
- boolean wasSummary = (old.mOriginalFlags & FLAG_GROUP_SUMMARY) != 0;
- boolean wasForcedGrouped = (old.getFlags() & FLAG_GROUP_SUMMARY) == 0
- && old.getSbn().getOverrideGroupKey() != null;
- if (n.isGroupSummary() && wasSummary && wasForcedGrouped) {
- n.flags &= ~FLAG_GROUP_SUMMARY;
- }
+ // If this is an update to a summary that was forced grouped => remove summary flag
+ if (wasSummaryAutogrouped(r, old)) {
+ n.flags &= ~FLAG_GROUP_SUMMARY;
}
}
@@ -11333,12 +11395,16 @@ 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_NAS_UNSUPPORTED = "unsupported_adjustments";
private final Object mLock = new Object();
@GuardedBy("mLock")
private Set<String> mAllowedAdjustments = new ArraySet<>();
+ @GuardedBy("mLock")
+ private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>();
+
protected ComponentName mDefaultFromConfig = null;
@Override
@@ -11831,6 +11897,10 @@ public class NotificationManagerService extends SystemService {
setNotificationAssistantAccessGrantedForUserInternal(
currentComponent, userId, false, userSet);
}
+ } else {
+ if (android.service.notification.Flags.notificationClassification()) {
+ mNasUnsupported.put(userId, new HashSet<>());
+ }
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
}
@@ -11838,6 +11908,63 @@ public class NotificationManagerService extends SystemService {
private boolean isVerboseLogEnabled() {
return Log.isLoggable("notification_assistant", Log.VERBOSE);
}
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public void setAdjustmentTypeSupportedState(ManagedServiceInfo info,
+ @Adjustment.Keys String key, boolean supported) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ HashSet<String> disabledAdjustments =
+ mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+ if (supported) {
+ disabledAdjustments.remove(key);
+ } else {
+ disabledAdjustments.add(key);
+ }
+ mNasUnsupported.put(info.userid, disabledAdjustments);
+ handleSavePolicyFile();
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @GuardedBy("mNotificationLock")
+ public @NonNull Set<String> getUnsupportedAdjustments(@UserIdInt int userId) {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return new HashSet<>();
+ }
+ return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+ }
+
+ @Override
+ protected void writeExtraAttributes(TypedXmlSerializer out, @UserIdInt int approvedUserId)
+ throws IOException {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mNasUnsupported.containsKey(approvedUserId)) {
+ out.attribute(null, ATT_NAS_UNSUPPORTED,
+ TextUtils.join(",", mNasUnsupported.get(approvedUserId)));
+ }
+ }
+ }
+
+ @Override
+ protected void readExtraAttributes(String tag, TypedXmlPullParser parser,
+ @UserIdInt int approvedUserId) throws IOException {
+ if (!android.service.notification.Flags.notificationClassification()) {
+ return;
+ }
+ if (ManagedServices.TAG_MANAGED_SERVICES.equals(tag)) {
+ final String types = XmlUtils.readStringAttribute(parser, ATT_NAS_UNSUPPORTED);
+ synchronized (mLock) {
+ if (!TextUtils.isEmpty(types)) {
+ mNasUnsupported.put(approvedUserId, new HashSet(List.of(types.split(","))));
+ }
+ }
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index f0358d1e1d8c..be34beeb1236 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -55,4 +55,9 @@ public interface NotificationSignalExtractor {
void setZenHelper(ZenModeHelper helper);
default void setCompatChangeLogger(IPlatformCompat platformCompat){};
+
+ /**
+ * @param groupHelper Helper for auto-grouping notifications
+ */
+ default void setGroupHelper(GroupHelper groupHelper){};
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 03dd9351efc7..f06d6405b3c2 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,7 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.text.TextUtils.formatSimple;
import android.annotation.NonNull;
-import android.app.NotificationManager;
import android.content.Context;
import android.service.notification.RankingHelperProto;
import android.util.ArrayMap;
@@ -61,7 +60,7 @@ public class RankingHelper {
})
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
- IPlatformCompat platformCompat) {
+ IPlatformCompat platformCompat, GroupHelper groupHelper) {
mContext = context;
mRankingHandler = rankingHandler;
if (sortSectionByTime()) {
@@ -80,6 +79,7 @@ public class RankingHelper {
extractor.initialize(mContext, usageStats);
extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
+ extractor.setGroupHelper(groupHelper);
if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
|| restrictAudioAttributesCall()) {
extractor.setCompatChangeLogger(platformCompat);
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index ff263d1467c3..bdca555707e3 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -37,7 +37,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
@@ -173,13 +172,6 @@ public class ZenModeFiltering {
maybeLogInterceptDecision(record, false, "criticalNotification");
return false;
}
- // Make an exception to policy for the notification saying that policy has changed
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
- && "android".equals(record.getSbn().getPackageName())
- && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
- maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
- return false;
- }
switch (zen) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
// #notevenalarms
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 626c3ddd49d9..ea211a9ab806 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -54,10 +54,8 @@ import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -74,7 +72,6 @@ import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -90,7 +87,6 @@ import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
@@ -117,8 +113,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -309,7 +303,6 @@ public class ZenModeHelper {
mHandler.postMetricsTimer();
cleanUpZenRules();
mIsSystemServicesReady = true;
- showZenUpgradeNotification(mZenMode);
}
/**
@@ -485,7 +478,7 @@ public class ZenModeHelper {
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
- maybeReplaceDefaultRule(newConfig, automaticZenRule);
+ maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
return rule.id;
@@ -535,13 +528,24 @@ public class ZenModeHelper {
return ruleToRestore;
}
- private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+ /**
+ * Possibly delete built-in rules if a more suitable rule is added or updated.
+ *
+ * <p>Today, this is done in one case: delete a disabled "Sleeping" rule if a Bedtime Mode is
+ * added (or an existing mode is turned into {@link AutomaticZenRule#TYPE_BEDTIME}, when
+ * upgrading). Because only the {@code config_systemWellbeing} package is allowed to use rules
+ * of this type, this will not trigger wantonly.
+ *
+ * @param oldRule If non-null, {@code rule} is updating {@code oldRule}. Otherwise,
+ * {@code rule} is being added.
+ */
+ private static void maybeReplaceDefaultRule(ZenModeConfig config, @Nullable ZenRule oldRule,
+ AutomaticZenRule rule) {
if (!Flags.modesApi()) {
return;
}
- if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
- // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
- // smarter triggers and it will prevent confusion about which one to use.
+ if (rule.getType() == AutomaticZenRule.TYPE_BEDTIME
+ && (oldRule == null || oldRule.type != rule.getType())) {
// Note: we must not verify canManageAutomaticZenRule here, since most likely they
// won't have the same owner (sleeping - system; bedtime - DWB).
ZenRule sleepingRule = config.automaticRules.get(
@@ -589,6 +593,10 @@ public class ZenModeHelper {
// condition) when no changes happen.
return true;
}
+
+ if (Flags.modesUi()) {
+ maybeReplaceDefaultRule(newConfig, oldRule, automaticZenRule);
+ }
return setConfigLocked(newConfig, origin, reason,
newRule.component, true, callingUid);
}
@@ -1584,8 +1592,6 @@ public class ZenModeHelper {
String reason, @Nullable String caller, int callingUid) {
setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
@@ -1783,17 +1789,6 @@ public class ZenModeHelper {
SystemZenRules.maybeUpgradeRules(mContext, config);
}
- // Resolve user id for settings.
- userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
- if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
- } else {
- // devices not restoring/upgrading already have updated zen settings
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
- }
-
if (Flags.modesApi() && forRestore) {
// Note: forBackup doesn't write deletedRules, but just in case.
config.deletedRules.clear();
@@ -2062,7 +2057,6 @@ public class ZenModeHelper {
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
"updated setting");
- showZenUpgradeNotification(zen);
}
private int getPreviousRingerModeSetting() {
@@ -2117,12 +2111,6 @@ public class ZenModeHelper {
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isActive()) {
if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- // automatic rule triggered dnd and user hasn't seen update dnd dialog
- if (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, 1) == 0) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 1);
- }
zen = automaticRule.zenMode;
}
}
@@ -2702,62 +2690,6 @@ public class ZenModeHelper {
}
}
- private void showZenUpgradeNotification(int zen) {
- final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- final boolean showNotification = mIsSystemServicesReady
- && zen != Global.ZEN_MODE_OFF
- && !isWatch
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0
- && Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ZEN_SETTINGS_UPDATED, 0) != 1;
-
- if (isWatch) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
-
- if (showNotification) {
- mNotificationManager.notify(TAG, SystemMessage.NOTE_ZEN_UPGRADE,
- createZenUpgradeNotification());
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- }
- }
-
- @VisibleForTesting
- protected Notification createZenUpgradeNotification() {
- final Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mContext.getResources().getString(R.string.global_action_settings));
- int title = R.string.zen_upgrade_notification_title;
- int content = R.string.zen_upgrade_notification_content;
- int drawable = R.drawable.ic_zen_24dp;
- if (NotificationManager.Policy.areAllVisualEffectsSuppressed(
- getConsolidatedNotificationPolicy().suppressedVisualEffects)) {
- title = R.string.zen_upgrade_notification_visd_title;
- content = R.string.zen_upgrade_notification_visd_content;
- drawable = R.drawable.ic_dnd_block_notifications;
- }
-
- Intent onboardingIntent = new Intent(Settings.ZEN_MODE_ONBOARDING);
- onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return new Notification.Builder(mContext, SystemNotificationChannels.DO_NOT_DISTURB)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_settings_24dp)
- .setLargeIcon(Icon.createWithResource(mContext, drawable))
- .setContentTitle(mContext.getResources().getString(title))
- .setContentText(mContext.getResources().getString(content))
- .setContentIntent(PendingIntent.getActivity(mContext, 0, onboardingIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .addExtras(extras)
- .setStyle(new Notification.BigTextStyle())
- .build();
- }
-
private int drawableResNameToResId(String packageName, String resourceName) {
if (TextUtils.isEmpty(resourceName)) {
return 0;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 89ced12e873d..4665a72b0b06 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2208,10 +2208,10 @@ public class ComputerEngine implements Computer {
return true;
}
boolean permissionGranted = requireFullPermission ? hasPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
: (hasPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
- || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid));
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS));
if (!permissionGranted) {
if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) {
return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission);
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 1569fa0aa8d7..02afdd662b10 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -80,6 +80,7 @@ import com.android.server.pinner.PinnerService;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.local.PackageManagerLocalImpl;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -819,10 +820,16 @@ public final class DexOptHelper {
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
+ PackageSetting uncommittedPs = null;
+ if (Flags.improveInstallFreeze()) {
+ uncommittedPs = ps;
+ }
+
PackageManagerLocal packageManagerLocal =
LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
+ PackageManagerLocalImpl.withFilteredSnapshot(packageManagerLocal,
+ uncommittedPs)) {
boolean ignoreDexoptProfile =
(installRequest.getInstallFlags()
& PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index aca65bfd561c..83292b775ddc 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -148,6 +148,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.ExceptionUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -1014,13 +1015,17 @@ final class InstallPackageHelper {
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
-
if (prepareInstallPackages(requests)
&& scanInstallPackages(requests, createdAppId, versionInfos)) {
List<ReconciledPackage> reconciledPackages =
reconcileInstallPackages(requests, versionInfos);
- if (reconciledPackages != null
- && renameAndUpdatePaths(requests)
+ if (reconciledPackages == null) {
+ return;
+ }
+ if (Flags.improveInstallFreeze()) {
+ prepPerformDexoptIfNeeded(reconciledPackages);
+ }
+ if (renameAndUpdatePaths(requests)
&& commitInstallPackages(reconciledPackages)) {
success = true;
}
@@ -1031,6 +1036,75 @@ final class InstallPackageHelper {
}
}
+ private int[] getNewUsers(InstallRequest installRequest, int[] allUsers)
+ throws PackageManagerException {
+ final int userId = installRequest.getUserId();
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
+ && !mPm.mUserManager.exists(userId)) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ }
+
+ final IntArray newUserIds = new IntArray();
+ if (userId != UserHandle.USER_ALL) {
+ newUserIds.add(userId);
+ } else if (allUsers != null) {
+ final int[] installedForUsers = installRequest.getOriginUsers();
+ for (int currentUserId : allUsers) {
+ final boolean installedForCurrentUser = ArrayUtils.contains(
+ installedForUsers, currentUserId);
+ final boolean restrictedByPolicy =
+ mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_INSTALL_APPS)
+ || mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ if (installedForCurrentUser || !restrictedByPolicy) {
+ newUserIds.add(currentUserId);
+ }
+ }
+ }
+
+ if (newUserIds.size() == 0) {
+ throw new PackageManagerException(PackageManagerException.INTERNAL_ERROR_MISSING_USER,
+ "User " + userId + " doesn't exist or has been removed");
+ } else {
+ return newUserIds.toArray();
+ }
+ }
+
+ private void prepPerformDexoptIfNeeded(List<ReconciledPackage> reconciledPackages) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
+ final InstallRequest request = reconciledPkg.mInstallRequest;
+ // prepare profiles
+ final PackageSetting ps = request.getScannedPackageSetting();
+ final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
+ final int[] allUsers = mPm.mUserManager.getUserIds();
+ if (reconciledPkg.mCollectedSharedLibraryInfos != null
+ || (oldPkgSetting != null
+ && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
+ // Reconcile if the new package or the old package uses shared libraries.
+ // It is possible that the old package uses shared libraries but the new
+ // one doesn't.
+ mSharedLibraries.executeSharedLibrariesUpdate(request.getParsedPackage(), ps,
+ null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
+ }
+ try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ final int[] newUsers = getNewUsers(request, allUsers);
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, newUsers);
+ if (request.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
+ } catch (PackageManagerException e) {
+ request.setError(e.error, e.getMessage());
+ return;
+ }
+ DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null);
+ }
+ }
+
private boolean renameAndUpdatePaths(List<InstallRequest> requests) {
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
for (InstallRequest request : requests) {
@@ -2655,20 +2729,22 @@ final class InstallPackageHelper {
incrementalStorages.add(storage);
}
- // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
- mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
- if (installRequest.isClearCodeCache()) {
- mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
- | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- }
if (installRequest.isInstallReplace() && pkg != null) {
mDexManager.notifyPackageUpdated(packageName,
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
+ if (!Flags.improveInstallFreeze()) {
+ // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
+ mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
+ if (installRequest.isClearCodeCache()) {
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
- DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
- mPm.mInstallLock.getRawLock());
+ DexOptHelper.performDexoptIfNeeded(installRequest, mDexManager, mContext,
+ mPm.mInstallLock.getRawLock());
+ }
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8657de24d725..5653da07779b 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -716,7 +716,7 @@ public class LauncherAppsService extends SystemService {
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
- mPackageManagerInternal.getInstalledApplicationsCrossUser(
+ mPackageManagerInternal.getInstalledApplications(
/* flags= */ 0, user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index cf5de897cf5d..a28e3c142220 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -244,7 +244,7 @@ class PackageMonitorCallbackHelper {
return;
}
int registerUid = registerUser.getUid();
- if (allowUids != null && registerUid != Process.SYSTEM_UID
+ if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
&& !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 55afb17614af..c22e382682a7 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -31,6 +31,7 @@ import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.snapshot.PackageDataSnapshot;
@@ -71,8 +72,26 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@NonNull
@Override
public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) {
+ return withFilteredSnapshot(callingUid, user, /* uncommittedPs= */ null);
+ }
+
+ /**
+ * Creates a {@link FilteredSnapshot} with a uncommitted {@link PackageState} that is used for
+ * dexopt in the art service to get the correct package state before the package is committed.
+ */
+ @NonNull
+ public static FilteredSnapshotImpl withFilteredSnapshot(PackageManagerLocal pm,
+ @NonNull PackageState uncommittedPs) {
+ return ((PackageManagerLocalImpl) pm).withFilteredSnapshot(Binder.getCallingUid(),
+ Binder.getCallingUserHandle(), uncommittedPs);
+ }
+
+ @NonNull
+ private FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user,
+ @Nullable PackageState uncommittedPs) {
return new FilteredSnapshotImpl(callingUid, user,
- mService.snapshotComputer(false /*allowLiveComputer*/), null);
+ mService.snapshotComputer(/* allowLiveComputer= */ false),
+ /* parentSnapshot= */ null, uncommittedPs);
}
@Override
@@ -145,7 +164,8 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Override
public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
- return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this);
+ return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this,
+ /* uncommittedPs= */ null);
}
@SuppressWarnings("RedundantSuppression")
@@ -209,13 +229,18 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Nullable
private final UnfilteredSnapshotImpl mParentSnapshot;
+ @Nullable
+ private final PackageState mUncommitPackageState;
+
private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
@NonNull PackageDataSnapshot snapshot,
- @Nullable UnfilteredSnapshotImpl parentSnapshot) {
+ @Nullable UnfilteredSnapshotImpl parentSnapshot,
+ @Nullable PackageState uncommittedPs) {
super(snapshot);
mCallingUid = callingUid;
mUserId = user.getIdentifier();
mParentSnapshot = parentSnapshot;
+ mUncommitPackageState = uncommittedPs;
}
@Override
@@ -237,6 +262,10 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
@Override
public PackageState getPackageState(@NonNull String packageName) {
checkClosed();
+ if (mUncommitPackageState != null
+ && packageName.equals(mUncommitPackageState.getPackageName())) {
+ return mUncommitPackageState;
+ }
return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}
@@ -250,6 +279,11 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
var filteredPackageStates = new ArrayMap<String, PackageState>();
for (int index = 0, size = packageStates.size(); index < size; index++) {
var packageState = packageStates.valueAt(index);
+ if (mUncommitPackageState != null
+ && packageState.getPackageName().equals(
+ mUncommitPackageState.getPackageName())) {
+ packageState = (PackageStateInternal) mUncommitPackageState;
+ }
if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
filteredPackageStates.put(packageStates.keyAt(index), packageState);
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6e7a009f58ce..bc6a40abaee3 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -884,10 +884,13 @@ final class DefaultPermissionGrantPolicy {
}
// Allow voice search on wear
- grantPermissionsToSystemPackage(pm,
- getDefaultSystemHandlerActivityPackage(pm,
- SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
- userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
+ String voiceSearchPackage = getDefaultSystemHandlerActivityPackage(pm,
+ SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId);
+ grantPermissionsToSystemPackage(pm, voiceSearchPackage,
+ userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS,
+ COARSE_BACKGROUND_LOCATION_PERMISSIONS);
+ revokeRuntimePermissions(pm, voiceSearchPackage,
+ FINE_LOCATION_PERMISSIONS, false, userId);
}
// Print Spooler
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 027e69cbc09b..66ec53e6500e 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -667,9 +667,11 @@ public class ModifierShortcutManager {
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
List<KeyboardShortcutInfo> shortcuts = new ArrayList();
if (modifierShortcutManagerRefactor()) {
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
for (Bookmark b : mBookmarks.values()) {
KeyboardShortcutInfo info = shortcutInfoFromIntent(
- b.getShortcutChar(), b.getIntent(mContext), b.isShift());
+ b.getShortcutChar(), b.getIntent(context), b.isShift());
if (info != null) {
shortcuts.add(info);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 197f0070b553..e47b4c2ee147 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -373,6 +373,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
//The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS
static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0;
static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1;
+ static final int DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP = 2;
// Must match: config_triplePressOnStemPrimaryBehavior in config.xml
// The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS
@@ -1596,6 +1597,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
performStemPrimaryDoublePressSwitchToRecentTask();
}
break;
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ final int stemPrimaryKeyDeviceId = INVALID_INPUT_DEVICE_ID;
+ handleKeyGestureInKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
+ stemPrimaryKeyDeviceId, KEYCODE_STEM_PRIMARY, /* metaState= */ 0);
+ break;
}
}
@@ -7244,6 +7251,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return "DOUBLE_PRESS_PRIMARY_NOTHING";
case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
return "DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP";
+ case DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP:
+ return "DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP";
default:
return Integer.toString(behavior);
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 4fae798f0cef..eb62b5631c43 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -709,7 +709,7 @@ public class Notifier {
SparseBooleanArray newDisplayInteractivities = new SparseBooleanArray();
for (int i = 0; i < displaysByGroupId.size(); i++) {
final int groupId = displaysByGroupId.keyAt(i);
- for (int displayId : displaysByGroupId.get(i)) {
+ for (int displayId : displaysByGroupId.get(groupId)) {
// If we already know display interactivity, use that
if (mDisplayInteractivities.indexOfKey(displayId) > 0) {
newDisplayInteractivities.put(
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 1346a294b7d8..c969eff32f8e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -21,6 +21,7 @@ import static android.os.Flags.adpfUseFmqChannel;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.adpfSessionTag;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
+import static com.android.server.power.hint.Flags.resetOnForkEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1057,6 +1058,11 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ for (int tid : tids) {
+ Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ }
+ }
if (adpfSessionTag() && tag == SessionTag.APP) {
// If the category of the app is a game,
@@ -1282,11 +1288,9 @@ public final class HintManagerService extends SystemService {
boolean updateHintAllowedByProcState(boolean allowed) {
synchronized (this) {
if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) {
- Slogf.e(TAG, "ADPF IS GETTING RESUMED? UID: " + mUid + " TAG: " + mTag);
resume();
}
if (!allowed && mUpdateAllowedByProcState) {
- Slogf.e(TAG, "ADPF IS GETTING PAUSED? UID: " + mUid + " TAG: " + mTag);
pause();
}
mUpdateAllowedByProcState = allowed;
@@ -1449,6 +1453,11 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
+ if (resetOnForkEnabled()){
+ for (int tid : tids) {
+ Process.setThreadScheduler(tid, Process.SCHED_RESET_ON_FORK, 0);
+ }
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
for (int i = nonIsolated.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index 55afa05f66fe..e56b68c93480 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -14,3 +14,10 @@ flag {
description: "Feature flag for adding session tag to hint session atom"
bug: "345011125"
}
+
+flag {
+ name: "reset_on_fork_enabled"
+ namespace: "game"
+ description: "Set reset_on_fork flag."
+ bug: "370988407"
+}
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index b5a7fcb72982..e650c52b68b4 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -56,24 +56,26 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public long getCreateUptimeMillis() {
+ return stats.getCreateUptimeMillis();
+ }
+
+ @Override
public CallerInfo getCallerInfo() {
return callerInfo;
}
@Override
- public VibrationSession.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
- /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
- callerInfo);
+ public IBinder getCallerToken() {
+ return mExternalVibration.getToken();
}
@Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- return new VibrationStats.StatsInfo(
- mExternalVibration.getUid(),
- FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
- mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
- completionUptimeMillis);
+ public VibrationSession.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
+ /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
+ mScale.adaptiveHapticsScale);
}
@Override
@@ -86,6 +88,12 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public boolean wasEndRequested() {
+ // End request is immediate, so just check if vibration has already ended.
+ return hasEnded();
+ }
+
+ @Override
public boolean linkToDeath(Runnable callback) {
synchronized (mLock) {
mBinderDeathCallback = callback;
@@ -104,10 +112,12 @@ final class ExternalVibrationSession extends Vibration
@Override
public void binderDied() {
+ Runnable callback;
synchronized (mLock) {
- if (mBinderDeathCallback != null) {
- mBinderDeathCallback.run();
- }
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
}
}
@@ -131,6 +141,16 @@ final class ExternalVibrationSession extends Vibration
end(new EndInfo(status, endedBy));
}
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // ignored, external control does not expect callbacks from the vibrator manager
+ }
+
boolean isHoldingSameVibration(ExternalVibration vib) {
return mExternalVibration.equals(vib);
}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index ce9c47ba6ba4..fbcc856d0974 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -19,15 +19,16 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
-import com.android.internal.util.FrameworkStatsLog;
-
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.IntFunction;
/**
* Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -36,7 +37,6 @@ import java.util.concurrent.CountDownLatch;
final class HalVibration extends Vibration {
public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
- public final IBinder callerToken;
/** A {@link CountDownLatch} to enable waiting for completion. */
private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -56,10 +56,9 @@ final class HalVibration extends Vibration {
private int mScaleLevel;
private float mAdaptiveScale;
- HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect,
- @NonNull VibrationSession.CallerInfo callerInfo) {
+ HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
+ @NonNull CombinedVibration effect) {
super(callerInfo);
- this.callerToken = callerToken;
mOriginalEffect = effect;
mEffectToPlay = effect;
mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -87,11 +86,11 @@ final class HalVibration extends Vibration {
}
/**
- * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
- * which might be necessary for replacement in realtime.
+ * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
+ * might be necessary for replacement in realtime.
*/
- public void addFallback(int effectId, VibrationEffect effect) {
- mFallbacks.put(effectId, effect);
+ public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
+ fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
}
/**
@@ -131,11 +130,6 @@ final class HalVibration extends Vibration {
// No need to update fallback effects, they are already configured per device.
}
- @Override
- public boolean isRepeating() {
- return mOriginalEffect.getDuration() == Long.MAX_VALUE;
- }
-
/** Return the effect that should be played by this vibration. */
public CombinedVibration getEffectToPlay() {
return mEffectToPlay;
@@ -146,20 +140,9 @@ final class HalVibration extends Vibration {
// Clear the original effect if it's the same as the effect that was played, for simplicity
CombinedVibration originalEffect =
Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
- return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
- mScaleLevel, mAdaptiveScale, callerInfo);
- }
-
- @Override
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- int vibrationType = mEffectToPlay.hasVendorEffects()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
- : isRepeating()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
- : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
- return new VibrationStats.StatsInfo(
- callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
- stats, completionUptimeMillis);
+ return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
+ originalEffect, mScaleLevel, mAdaptiveScale);
}
/**
@@ -174,6 +157,42 @@ final class HalVibration extends Vibration {
return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
VibrationAttributes.FLAG_PIPELINED_EFFECT)
&& vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && !isRepeating();
+ && (mOriginalEffect.getDuration() != Long.MAX_VALUE);
+ }
+
+ private void fillFallbacksForEffect(CombinedVibration effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (effect instanceof CombinedVibration.Mono) {
+ fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
+ } else if (effect instanceof CombinedVibration.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibration.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
+ }
+ } else if (effect instanceof CombinedVibration.Sequential) {
+ List<CombinedVibration> effects =
+ ((CombinedVibration.Sequential) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ fillFallbacksForEffect(effects.get(i), fallbackProvider);
+ }
+ }
+ }
+
+ private void fillFallbacksForEffect(VibrationEffect effect,
+ IntFunction<VibrationEffect> fallbackProvider) {
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
+ return;
+ }
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
+ VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
+ if (fallback != null) {
+ mFallbacks.put(prebaked.getEffectId(), fallback);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
new file mode 100644
index 000000000000..f80407d03e5c
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session holding a single {@link CombinedVibration} request, performed by a
+ * {@link VibrationStepConductor}.
+ */
+final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
+ private static final String TAG = "SingleVibrationSession";
+
+ private final Object mLock = new Object();
+ private final IBinder mCallerToken;
+ private final HalVibration mVibration;
+
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mBinderDeathCallback;
+
+ SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
+ @NonNull CombinedVibration vibration) {
+ mCallerToken = callerToken;
+ mVibration = new HalVibration(callerInfo, vibration);
+ }
+
+ public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ mConductor = conductor;
+ }
+ }
+
+ public HalVibration getVibration() {
+ return mVibration;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mVibration.stats.getCreateUptimeMillis();
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mVibration.callerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallerToken;
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ return mVibration.getDebugInfo();
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ if (mVibration.hasEnded()) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mConductor != null && mConductor.wasNotifiedToCancel();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ Runnable callback;
+ synchronized (mLock) {
+ callback = mBinderDeathCallback;
+ }
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public boolean linkToDeath(@Nullable Runnable callback) {
+ synchronized (mLock) {
+ mBinderDeathCallback = callback;
+ }
+ try {
+ mCallerToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking vibration to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallerToken.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
+ }
+ synchronized (mLock) {
+ mBinderDeathCallback = null;
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
+ } else {
+ mVibration.end(new Vibration.EndInfo(status, endedBy));
+ }
+ }
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifyVibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ if (vibrationId != mVibration.id) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mConductor != null) {
+ mConductor.notifySyncedVibrationComplete();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 21fd4ce0acd0..bb2a17c698ee 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -88,15 +88,9 @@ abstract class Vibration {
stats.reportEnded(endInfo.endedBy);
}
- /** Return true if vibration is a repeating vibration. */
- abstract boolean isRepeating();
-
/** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
abstract VibrationSession.DebugInfo getDebugInfo();
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
- abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
-
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
/** The vibration status to be set when it ends with this info. */
@@ -146,35 +140,41 @@ abstract class Vibration {
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfoImpl implements VibrationSession.DebugInfo {
- final VibrationSession.Status mStatus;
- final long mCreateTime;
- final VibrationSession.CallerInfo mCallerInfo;
+ private final VibrationSession.Status mStatus;
+ private final VibrationStats.StatsInfo mStatsInfo;
+ private final VibrationSession.CallerInfo mCallerInfo;
@Nullable
- final CombinedVibration mPlayedEffect;
-
- private final long mStartTime;
- private final long mEndTime;
- private final long mDurationMs;
+ private final CombinedVibration mPlayedEffect;
@Nullable
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
- @Nullable CombinedVibration playedEffect,
- @Nullable CombinedVibration originalEffect, int scaleLevel,
- float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+
+ DebugInfoImpl(VibrationSession.Status status,
+ @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
+ VibrationStats stats, @Nullable CombinedVibration playedEffect,
+ @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
Objects.requireNonNull(callerInfo);
- mCreateTime = stats.getCreateTimeDebug();
- mStartTime = stats.getStartTimeDebug();
- mEndTime = stats.getEndTimeDebug();
- mDurationMs = stats.getDurationDebug();
+ mCallerInfo = callerInfo;
+ mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
+ callerInfo.attrs.getUsage(), status);
mPlayedEffect = playedEffect;
mOriginalEffect = originalEffect;
mScaleLevel = scaleLevel;
mAdaptiveScale = adaptiveScale;
- mCallerInfo = callerInfo;
mStatus = status;
+
+ mCreateUptime = stats.getCreateUptimeMillis();
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
}
@Override
@@ -184,7 +184,7 @@ abstract class Vibration {
@Override
public long getCreateUptimeMillis() {
- return mCreateTime;
+ return mCreateUptime;
}
@Override
@@ -216,6 +216,7 @@ abstract class Vibration {
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ statsLogger.writeVibrationReportedAsync(mStatsInfo);
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index 70477a26b759..4de8f78f7836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -39,9 +39,18 @@ import java.util.Objects;
*/
interface VibrationSession {
+ /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+ long getCreateUptimeMillis();
+
+ /** Return true if vibration session plays a repeating vibration. */
+ boolean isRepeating();
+
/** Returns data about the client app that triggered this vibration session. */
CallerInfo getCallerInfo();
+ /** Returns the binder token from the client app attached to this vibration session. */
+ IBinder getCallerToken();
+
/** Returns debug data for logging and metric reports. */
DebugInfo getDebugInfo();
@@ -58,6 +67,19 @@ interface VibrationSession {
/** Removes link to the app process death. */
void unlinkToDeath();
+ /** Returns true if this session was requested to end by {@link #requestEnd}. */
+ boolean wasEndRequested();
+
+ /**
+ * Request the end of this session, which might be acted upon asynchronously.
+ *
+ * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
+ * {@link CallerInfo} and with {@code immediate} flag set to false.
+ */
+ default void requestEnd(@NonNull Status status) {
+ requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
+ }
+
/**
* Notify the session end was requested, which might be acted upon asynchronously.
*
@@ -71,6 +93,25 @@ interface VibrationSession {
void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);
/**
+ * Notify a vibrator has completed the last command during the playback of given vibration.
+ *
+ * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
+ * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
+ * since its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifyVibratorCallback(int vibratorId, long vibrationId);
+
+ /**
+ * Notify all synced vibrators have completed the last synchronized command during the playback
+ * of given vibration.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the last
+ * synchronized vibrate call is complete. This does not mean the vibration is complete, since
+ * its playback might have one or more interactions with the vibrator hardware.
+ */
+ void notifySyncedVibratorsCallback(long vibrationId);
+
+ /**
* Session status with reference to values from vibratormanagerservice.proto for logging.
*/
enum Status {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index fc0c6e7bf05e..637a5a180063 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -18,6 +18,7 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
@@ -37,11 +38,11 @@ final class VibrationStats {
// vibrate request.
// - Start: time a vibration started to play, which is closer to the time that the
// VibrationEffect started playing the very first segment.
- // - End: time a vibration ended, even if it never started to play. This can be as soon as the
- // vibrator HAL reports it has finished the last command, or before it has even started
- // when the vibration is ignored or cancelled.
- // Create and end times set by VibratorManagerService only, guarded by its lock.
- // Start times set by VibrationThread only (single-threaded).
+ // - End: time a vibration ended with a status, even if it never started to play. This can be as
+ // soon as the vibrator HAL reports it has finished the last command, or before it has
+ // even started when the vibration is ignored or cancelled.
+ // Created and ended times set by VibratorManagerService only, guarded by its lock.
+ // Start time set by VibrationThread only (single-threaded).
private long mCreateUptimeMillis;
private long mStartUptimeMillis;
private long mEndUptimeMillis;
@@ -97,6 +98,10 @@ final class VibrationStats {
mInterruptedUsage = -1;
}
+ StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) {
+ return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this);
+ }
+
long getCreateUptimeMillis() {
return mCreateUptimeMillis;
}
@@ -300,7 +305,7 @@ final class VibrationStats {
* {@link com.android.internal.util.FrameworkStatsLog} as a
* {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
*/
- static final class StatsInfo {
+ public static final class StatsInfo {
public final int uid;
public final int vibrationType;
public final int usage;
@@ -331,7 +336,7 @@ final class VibrationStats {
private boolean mIsWritten;
StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
- VibrationStats stats, long completionUptimeMillis) {
+ VibrationStats stats) {
this.uid = uid;
this.vibrationType = vibrationType;
this.usage = usage;
@@ -342,6 +347,9 @@ final class VibrationStats {
interruptedUsage = stats.mInterruptedUsage;
repeatCount = stats.mRepeatCount;
+ // Consider this vibration is being completed now.
+ long completionUptimeMillis = SystemClock.uptimeMillis();
+
// This duration goes from the time this object was created until the time it was
// completed. We can use latencies to detect the times between first and last
// interaction with vibrator.
@@ -419,5 +427,25 @@ final class VibrationStats {
}
return res;
}
+
+ /**
+ * Returns the vibration type value from {@code ReportedVibration} that best represents this
+ * {@link CombinedVibration}.
+ *
+ * <p>This does not include external vibrations, as those are not represented by a single
+ * vibration effect.
+ */
+ public static int findVibrationType(@Nullable CombinedVibration effect) {
+ if (effect == null) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
+ if (effect.hasVendorEffects()) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED;
+ }
+ return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1d52e3c87d17..4bb0c16d9655 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
@@ -39,7 +37,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CancellationException;
@@ -55,7 +52,7 @@ import java.util.concurrent.TimeoutException;
* VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
* methods (which should never be used from the VibrationThread thread).
*/
-final class VibrationStepConductor implements IBinder.DeathRecipient {
+final class VibrationStepConductor {
private static final boolean DEBUG = VibrationThread.DEBUG;
private static final String TAG = VibrationThread.TAG;
@@ -346,42 +343,6 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
/**
- * Binder death notification. VibrationThread registers this when it's running a conductor.
- * Note that cancellation could theoretically happen immediately, before the conductor has
- * started, but in this case it will be processed in the first signals loop.
- */
- @Override
- public void binderDied() {
- if (DEBUG) {
- Slog.d(TAG, "Binder died, cancelling vibration...");
- }
- notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
- /* immediate= */ false);
- }
-
- /**
- * Returns true if successfully linked this conductor to the death of the binder that requested
- * the vibration.
- */
- public boolean linkToDeath() {
- try {
- mVibration.callerToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error linking vibration to token death", e);
- return false;
- }
- return true;
- }
-
- public void unlinkToDeath() {
- try {
- mVibration.callerToken.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
- }
- }
-
- /**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 07473d10b217..9b7bdece69f9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -51,7 +51,6 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
@@ -159,9 +158,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
- private VibrationStepConductor mCurrentVibration;
+ private SingleVibrationSession mCurrentVibration;
@GuardedBy("mLock")
- private VibrationStepConductor mNextVibration;
+ private SingleVibrationSession mNextVibration;
@GuardedBy("mLock")
private ExternalVibrationSession mCurrentExternalVibration;
@GuardedBy("mLock")
@@ -188,24 +187,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF));
+ clearNextVibrationLocked(Status.CANCELLED_BY_SCREEN_OFF);
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SCREEN_OFF);
}
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
if (shouldCancelOnFgUserRequest(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER));
+ clearNextVibrationLocked(Status.CANCELLED_BY_FOREGROUND_USER);
}
if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_FOREGROUND_USER);
}
}
}
@@ -222,12 +217,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
synchronized (mLock) {
if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
- clearNextVibrationLocked(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS));
+ clearNextVibrationLocked(Status.CANCELLED_BY_APP_OPS);
}
if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
- Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_APP_OPS);
}
}
}
@@ -602,8 +595,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
- HalVibration vib = new HalVibration(token, effect, callerInfo);
- fillVibrationFallbacks(vib, effect);
+ SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
+ HalVibration vib = session.getVibration();
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
// Force update of user settings before checking if this vibration effect should
@@ -617,21 +611,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// Check if user settings or DnD is set to ignore this vibration.
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ CallerInfo ignoredBy = null;
// Check if ongoing vibration is more important than this vibration.
- if (vibrationEndInfo == null) {
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
}
// If not ignored so far then try to start this vibration.
- if (vibrationEndInfo == null) {
+ if (ignoreStatus == null) {
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentExternalVibration != null) {
vib.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
- endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, vib.callerInfo,
+ endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo,
/* continueExternalControl= */ false);
} else if (mCurrentVibration != null) {
if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
@@ -645,21 +644,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} else {
vib.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
/* immediate= */ false);
}
}
- vibrationEndInfo = startVibrationLocked(vib);
+ ignoreStatus = startVibrationLocked(session);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
// Ignored or failed to start the vibration, end it and report metrics right away.
- if (vibrationEndInfo != null) {
- endVibrationLocked(vib, vibrationEndInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus, ignoredBy);
}
return vib;
}
@@ -677,27 +674,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
- Vibration.EndInfo cancelledByUserInfo =
- new Vibration.EndInfo(Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
- if (mNextVibration != null
- && shouldCancelVibration(mNextVibration.getVibration(),
- usageFilter, token)) {
- clearNextVibrationLocked(cancelledByUserInfo);
+ if (shouldCancelVibration(mNextVibration, usageFilter, token)) {
+ clearNextVibrationLocked(Status.CANCELLED_BY_USER);
}
- if (mCurrentVibration != null
- && shouldCancelVibration(mCurrentVibration.getVibration(),
- usageFilter, token)) {
- mCurrentVibration.notifyCancelled(
- cancelledByUserInfo, /* immediate= */false);
+ if (shouldCancelVibration(mCurrentVibration, usageFilter, token)) {
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
}
- if (mCurrentExternalVibration != null
- && shouldCancelVibration(
- mCurrentExternalVibration.getCallerInfo().attrs,
- usageFilter)) {
- endExternalVibrateLocked(cancelledByUserInfo.status,
- cancelledByUserInfo.endedBy, /* continueExternalControl= */ false);
+ // TODO(b/370948466): investigate why token is not checked here and fix it.
+ if (shouldCancelVibration(mCurrentExternalVibration, usageFilter, null)) {
+ endExternalVibrateLocked(Status.CANCELLED_BY_USER,
+ /* endedBy= */ null, /* continueExternalControl= */ false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -842,18 +830,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return;
}
- HalVibration vib = mCurrentVibration.getVibration();
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
- if (inputDevicesChanged || (vibrationEndInfo != null)) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+ if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
- + (inputDevicesChanged ? "input devices changed"
- : vibrationEndInfo.status));
+ + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
- /* immediate= */ false);
+ mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
}
}
@@ -873,8 +856,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (vibrator == null) {
continue;
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
- if (vibrationEndInfo == null) {
+ Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -886,25 +869,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
+ private Status startVibrationLocked(SingleVibrationSession session) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
if (mInputDeviceDelegate.isAvailable()) {
- return startVibrationOnInputDevicesLocked(vib);
+ return startVibrationOnInputDevicesLocked(session.getVibration());
}
-
- VibrationStepConductor conductor = createVibrationStepConductor(vib);
-
if (mCurrentVibration == null) {
- return startVibrationOnThreadLocked(conductor);
+ return startVibrationOnThreadLocked(session);
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
- mNextVibration = conductor;
+ clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+ mNextVibration = session;
return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -913,50 +892,45 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
- HalVibration vib = conductor.getVibration();
- int mode = startAppOpModeLocked(vib.callerInfo);
+ private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+ VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
+ session.setVibrationConductor(conductor);
+ int mode = startAppOpModeLocked(session.getCallerInfo());
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
- mCurrentVibration = conductor;
- if (!mCurrentVibration.linkToDeath()) {
+ mCurrentVibration = session;
+ if (!mCurrentVibration.linkToDeath(null)) {
// Shouldn't happen. The method call already logs a wtf.
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN);
+ return Status.IGNORED_ERROR_TOKEN;
}
- if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
// Shouldn't happen. The method call already logs a wtf.
+ mCurrentVibration.setVibrationConductor(null);
mCurrentVibration = null; // Aborted.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
+ return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + vib.callerInfo.uid);
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
default:
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Status status) {
- endVibrationLocked(vib, new Vibration.EndInfo(status));
- }
-
- @GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo) {
- vib.end(vibrationEndInfo);
- reportEndedVibrationLocked(vib);
+ private void endVibrationLocked(VibrationSession session, Status status) {
+ endVibrationLocked(session, status, /* endedBy= */ null);
}
@GuardedBy("mLock")
- private void reportEndedVibrationLocked(Vibration vib) {
- logAndRecordVibration(vib.getDebugInfo());
- mFrameworkStatsLogger.writeVibrationReportedAsync(
- vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+ session.requestEnd(status, endedBy, /* immediate= */ false);
+ logAndRecordVibration(session.getDebugInfo());
}
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
@@ -975,12 +949,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
- private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
// Scale resolves the default amplitudes from the effect before scaling them.
vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
-
- return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
+ return Status.FORWARDED_TO_INPUT_DEVICES;
}
private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
@@ -994,9 +967,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
CallerInfo callerInfo, Status status) {
logAndRecordVibration(
- new Vibration.DebugInfoImpl(status, new VibrationStats(),
+ new Vibration.DebugInfoImpl(status, callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
- VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+ VibrationScaler.ADAPTIVE_SCALE_NONE));
}
private void logAndRecordVibration(DebugInfo info) {
@@ -1050,39 +1024,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- @GuardedBy("mLock")
- private void reportFinishedVibrationLocked() {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration.unlinkToDeath();
- HalVibration vib = mCurrentVibration.getVibration();
- if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vib.getStatus());
- }
- finishAppOpModeLocked(vib.callerInfo);
- reportEndedVibrationLocked(vib);
- }
-
private void onSyncedVibrationComplete(long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
}
- mCurrentVibration.notifySyncedVibrationComplete();
+ mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
}
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null
- && mCurrentVibration.getVibration().id == vibrationId) {
+ if (mCurrentVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
- mCurrentVibration.notifyVibratorComplete(vibratorId);
+ mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
}
}
}
@@ -1094,14 +1054,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+ private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
if (mCurrentExternalVibration != null) {
- return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentExternalVibration);
}
if (mNextVibration != null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
- mNextVibration.getVibration());
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
+ mNextVibration);
if (vibrationEndInfo != null) {
// Next vibration has higher importance than the new one, so the new vibration
// should be ignored.
@@ -1110,14 +1070,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
if (mCurrentVibration != null) {
- HalVibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
- // Current vibration has ended or is cancelling, should not block incoming
- // vibrations.
+ if (mCurrentVibration.wasEndRequested()) {
+ // Current session has ended or is cancelling, should not block incoming vibrations.
return null;
}
- return shouldIgnoreVibrationForOngoing(vib, currentVibration);
+ return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
}
return null;
@@ -1125,32 +1083,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Checks if the ongoing vibration has higher importance than the new one. If they have similar
- * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
+ * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker.
*
* @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
*/
@Nullable
private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
- @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {
+ @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
- int newVibrationImportance = getVibrationImportance(newVibration);
- int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);
+ int newSessionImportance = getVibrationImportance(newSession);
+ int ongoingSessionImportance = getVibrationImportance(ongoingSession);
- if (newVibrationImportance > ongoingVibrationImportance) {
+ if (newSessionImportance > ongoingSessionImportance) {
// New vibration has higher importance and should not be ignored.
return null;
}
- if (ongoingVibrationImportance > newVibrationImportance) {
+ if (ongoingSessionImportance > newSessionImportance) {
// Existing vibration has higher importance and should not be cancelled.
return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
- ongoingVibration.callerInfo);
+ ongoingSession.getCallerInfo());
}
// Same importance, use repeating as a tiebreaker.
- if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
+ if (ongoingSession.isRepeating() && !newSession.isRepeating()) {
// Ongoing vibration is repeating and new one is not, give priority to ongoing
- return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
+ return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING,
+ ongoingSession.getCallerInfo());
}
// New vibration is repeating or this is a complete tie between them,
// give priority to new vibration.
@@ -1164,10 +1123,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* @return a numeric representation for the vibration importance, larger values represent a
* higher importance
*/
- private static int getVibrationImportance(Vibration vibration) {
- int usage = vibration.callerInfo.attrs.getUsage();
+ private static int getVibrationImportance(VibrationSession session) {
+ int usage = session.getCallerInfo().attrs.getUsage();
if (usage == VibrationAttributes.USAGE_UNKNOWN) {
- if (vibration.isRepeating()) {
+ if (session.isRepeating()) {
usage = VibrationAttributes.USAGE_RINGTONE;
} else {
usage = VibrationAttributes.USAGE_TOUCH;
@@ -1201,10 +1160,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+ private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
if (statusFromSettings != null) {
- return new Vibration.EndInfo(statusFromSettings);
+ return statusFromSettings;
}
int mode = checkAppOpModeLocked(callerInfo);
@@ -1212,9 +1171,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
- return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
+ return Status.IGNORED_ERROR_APP_OPS;
} else {
- return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
+ return Status.IGNORED_APP_OPS;
}
}
@@ -1239,32 +1198,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
- * @param vib The ongoing or pending vibration to be cancelled.
+ * @param session The ongoing or pending vibration session to be cancelled.
* @param usageFilter The vibration usages to be cancelled, any bitwise combination of
* VibrationAttributes.USAGE_* values.
- * @param token The binder token to identify the vibration origin. Only vibrations
+ * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
- return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
- usageFilter);
- }
-
- /**
- * Return true if the external vibration usage belongs to given usage class.
- *
- * @param attrs The attributes of an ongoing or pending vibration to be cancelled.
- * @param usageFilter The vibration usages to be cancelled, any bitwise combination of
- * VibrationAttributes.USAGE_* values.
- */
- private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) {
- if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) {
+ private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+ @Nullable IBinder tokenFilter) {
+ if (session == null) {
+ return false;
+ }
+ if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
+ // Vibration from a different app, this should not cancel it.
+ return false;
+ }
+ int usage = session.getCallerInfo().attrs.getUsage();
+ if (usage == VibrationAttributes.USAGE_UNKNOWN) {
// Special case, usage UNKNOWN would match all filters. Instead it should only match if
// it's cancelling that usage specifically, or if cancelling all usages.
return usageFilter == VibrationAttributes.USAGE_UNKNOWN
|| usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL;
}
- return (usageFilter & attrs.getUsage()) == attrs.getUsage();
+ return (usageFilter & usage) == usage;
}
/**
@@ -1340,45 +1296,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
/**
- * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
- * VibrationSettings#getFallbackEffect}.
- */
- private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
- if (effect instanceof CombinedVibration.Mono) {
- fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
- } else if (effect instanceof CombinedVibration.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibration.Stereo) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.valueAt(i));
- }
- } else if (effect instanceof CombinedVibration.Sequential) {
- List<CombinedVibration> effects =
- ((CombinedVibration.Sequential) effect).getEffects();
- for (int i = 0; i < effects.size(); i++) {
- fillVibrationFallbacks(vib, effects.get(i));
- }
- }
- }
-
- private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
- if (!(effect instanceof VibrationEffect.Composed composed)) {
- return;
- }
- int segmentCount = composed.getSegments().size();
- for (int i = 0; i < segmentCount; i++) {
- VibrationEffectSegment segment = composed.getSegments().get(i);
- if (segment instanceof PrebakedSegment prebaked) {
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
- prebaked.getEffectId());
- if (prebaked.shouldFallback() && fallback != null) {
- vib.addFallback(prebaked.getEffectId(), fallback);
- }
- }
- }
- }
-
- /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -1475,30 +1392,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- HalVibration vib = conductor.getVibration();
- return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
- vib.stats.getCreateUptimeMillis());
+ return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(),
+ session.getCreateUptimeMillis());
}
@GuardedBy("mLock")
- private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return checkAppOpModeLocked(conductor.getVibration().callerInfo)
- != AppOpsManager.MODE_ALLOWED;
+ return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED;
}
@GuardedBy("mLock")
- private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) {
- if (conductor == null) {
+ private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) {
+ if (session == null) {
return false;
}
- return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM;
+ return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM;
}
@GuardedBy("mLock")
@@ -1660,17 +1575,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
if (mCurrentVibration != null) {
// This is when we consider the current vibration complete, report metrics.
- reportFinishedVibrationLocked();
+ if (DEBUG) {
+ Slog.d(TAG, "Reporting vibration " + vibrationId + " finished.");
+ }
+ mCurrentVibration.unlinkToDeath();
+ finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
+ logAndRecordVibration(mCurrentVibration.getDebugInfo());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = null;
}
if (mNextVibration != null) {
- VibrationStepConductor nextConductor = mNextVibration;
+ SingleVibrationSession nextVibration = mNextVibration;
mNextVibration = null;
- Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
- nextConductor);
- if (vibrationEndInfo != null) {
- // Failed to start the vibration, end it and report metrics right away.
- endVibrationLocked(nextConductor.getVibration(), vibrationEndInfo);
+ Status startErrorStatus = startVibrationOnThreadLocked(nextVibration);
+ if (startErrorStatus != null) {
+ endVibrationLocked(nextVibration, startErrorStatus);
}
}
}
@@ -1892,17 +1811,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mInfo.dump(proto, fieldId);
}
}
+ /** Clears mNextVibration if set, ending it cleanly */
+ @GuardedBy("mLock")
+ private void clearNextVibrationLocked(Status status) {
+ clearNextVibrationLocked(status, /* endedBy= */ null);
+ }
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
+ private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
if (mNextVibration != null) {
if (DEBUG) {
- Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id
- + " with end info: " + vibrationEndInfo);
+ Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+ + " with status: " + status);
}
// Clearing next vibration before playing it, end it and report metrics right away.
- endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo);
+ endVibrationLocked(mNextVibration, status, endedBy);
mNextVibration = null;
}
}
@@ -1927,7 +1851,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
setExternalControl(false, mCurrentExternalVibration.stats);
}
// The external control was turned off, end it and report metrics right away.
- reportEndedVibrationLocked(mCurrentExternalVibration);
+ logAndRecordVibration(mCurrentExternalVibration.getDebugInfo());
mCurrentExternalVibration = null;
}
@@ -1987,16 +1911,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
try {
// Create Vibration.Stats as close to the received request as possible, for
// tracking.
- ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
+ ExternalVibrationSession session = new ExternalVibrationSession(vib);
// Mute the request until we run all the checks and accept the vibration.
- externalVibration.muteScale();
+ session.muteScale();
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationLocked(externalVibration, Status.IGNORED_UNSUPPORTED);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+ return session.getScale();
}
if (ActivityManager.checkComponentPermission(
@@ -2006,29 +1930,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationLocked(externalVibration, Status.IGNORED_MISSING_PERMISSION);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+ return session.getScale();
}
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
- externalVibration.callerInfo);
+ Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
+ if (ignoreStatus != null) {
+ endVibrationLocked(session, ignoreStatus);
+ return session.getScale();
+ }
- if (vibrationEndInfo == null
- && mCurrentExternalVibration != null
+ if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
return mCurrentExternalVibration.getScale();
}
- if (vibrationEndInfo == null) {
- // Check if ongoing vibration is more important than this vibration.
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
- }
-
+ // Check if ongoing vibration is more important than this vibration.
+ Vibration.EndInfo vibrationEndInfo =
+ shouldIgnoreVibrationForOngoingLocked(session);
if (vibrationEndInfo != null) {
- endVibrationLocked(externalVibration, vibrationEndInfo);
- return externalVibration.getScale();
+ endVibrationLocked(session, vibrationEndInfo.status,
+ vibrationEndInfo.endedBy);
+ return session.getScale();
}
if (mCurrentExternalVibration == null) {
@@ -2036,15 +1961,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// vibration that may be playing and ready the vibrator for external
// control.
if (mCurrentVibration != null) {
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentVibration.getVibration().callerInfo);
- clearNextVibrationLocked(
- new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
- externalVibration.callerInfo));
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo),
- /* immediate= */ true);
+ clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL,
+ session.callerInfo);
+ mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+ session.callerInfo, /* immediate= */ true);
waitForCompletion = true;
}
} else {
@@ -2060,10 +1982,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// as we would need to mute the old one still if it came from a different
// controller.
alreadyUnderExternalControl = true;
- externalVibration.stats.reportInterruptedAnotherVibration(
+ session.stats.reportInterruptedAnotherVibration(
mCurrentExternalVibration.getCallerInfo());
endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED,
- externalVibration.callerInfo, /* continueExternalControl= */ true);
+ session.callerInfo, /* continueExternalControl= */ true);
}
}
// Wait for lock and interact with HAL to set external control outside main lock.
@@ -2071,8 +1993,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- endVibrationLocked(externalVibration, Status.IGNORED_ERROR_CANCELLING);
- return externalVibration.getScale();
+ endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+ return session.getScale();
}
}
}
@@ -2080,7 +2002,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true, externalVibration.stats);
+ setExternalControl(true, session.stats);
}
synchronized (mLock) {
if (DEBUG) {
@@ -2095,14 +2017,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibrationSettings.update();
}
- mCurrentExternalVibration = externalVibration;
- externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
- externalVibration.scale(mVibrationScaler, attrs.getUsage());
+ mCurrentExternalVibration = session;
+ session.linkToDeath(this::onExternalVibrationBinderDied);
+ session.scale(mVibrationScaler, attrs.getUsage());
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
- externalVibration.stats.reportStarted();
- return externalVibration.getScale();
+ session.stats.reportStarted();
+ return session.getScale();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5372abe970c7..0b36c7eb5fdf 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -230,6 +230,7 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -286,6 +287,9 @@ import android.app.servertransaction.StopActivityItem;
import android.app.servertransaction.TopResumedActivityChangeItem;
import android.app.servertransaction.TransferSplashScreenViewStateItem;
import android.app.usage.UsageEvents.Event;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -465,6 +469,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
+ @ChangeId
+ @Overridable
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415;
+
final ActivityTaskManagerService mAtmService;
final ActivityCallerState mCallerState;
@NonNull
@@ -2635,8 +2644,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
- // skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ // Skip copy splash screen to client if it was resized, or the starting data already
+ // requested to be removed after transaction commit.
+ || (mStartingData != null && (mStartingData.mResizedFromTransfer
+ || mStartingData.mRemoveAfterTransaction != AFTER_TRANSACTION_IDLE))
|| isRelaunching()) {
return false;
}
@@ -3179,11 +3190,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* will be ignored.
*/
boolean isUniversalResizeable() {
- return mWmService.mConstants.mIgnoreActivityOrientationRequest
- && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
- // If the user preference respects aspect ratio, then it becomes non-resizable.
- && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride();
+ if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
+ return false;
+ }
+ final boolean compatEnabled = Flags.universalResizableByDefault()
+ && mDisplayContent != null && mDisplayContent.getConfiguration()
+ .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ && mDisplayContent.getIgnoreOrientationRequest()
+ && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
+ return false;
+ }
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride();
}
boolean isResizeable() {
@@ -6409,11 +6429,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and the token could be null.
return;
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(r);
- if (cameraPolicy != null) {
- cameraPolicy.onActivityRefreshed(r);
- }
+ AppCompatCameraPolicy.onActivityRefreshed(r);
}
static void splashScreenAttachedLocked(IBinder token) {
@@ -8179,7 +8195,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
int candidateOrientation = super.getOverrideOrientation();
- if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
+ if (ActivityInfo.isFixedOrientation(candidateOrientation) && isUniversalResizeable()) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
@@ -9456,11 +9472,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- this);
- if (cameraPolicy != null) {
- cameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
- }
+ AppCompatCameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
}
/** Get process configuration, or global config if the process is not set. */
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 49380d4b8797..87fa62ac0e3b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2759,10 +2759,7 @@ class ActivityStarter {
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
- // Also put noDisplay activities in the source task. These by itself can be placed
- // in any task/root-task, however it could launch other activities like
- // ResolverActivity, and we want those to stay in the original task.
- if ((mStartActivity.isResolverOrDelegateActivity() || mStartActivity.noDisplay)
+ if (mStartActivity.isResolverOrDelegateActivity()
&& mSourceRecord != null && mSourceRecord.inFreeformWindowingMode()) {
mAddingToTask = true;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 0e666296dc33..d59046f44129 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -255,13 +255,10 @@ class AppCompatAspectRatioOverrides {
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
- && (cameraPolicy != null
- && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+ && AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3b023fe451bf..548c0a34bf99 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -74,11 +74,9 @@ class AppCompatAspectRatioPolicy {
@NonNull Rect parentBounds) {
// If in camera compat mode, aspect ratio from the camera compat policy has priority over
// default letterbox aspect ratio.
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- if (cameraPolicy != null && cameraPolicy.shouldCameraCompatControlAspectRatio(
+ if (AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
mActivityRecord)) {
- return cameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
+ return AppCompatCameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
}
final float letterboxAspectRatioOverride =
@@ -128,12 +126,8 @@ class AppCompatAspectRatioPolicy {
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
- && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
if (mActivityRecord.isUniversalResizeable()) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index f6090eb89345..1d00136ccfe1 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -70,9 +70,10 @@ class AppCompatCameraPolicy {
}
}
- void onActivityRefreshed(@NonNull ActivityRecord activity) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityRefreshed(activity);
+ static void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityRefreshed(activity);
}
}
@@ -88,10 +89,11 @@ class AppCompatCameraPolicy {
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*/
- void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ static void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
- if (mActivityRefresher != null) {
- mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) {
+ cameraPolicy.mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
lastReportedConfig);
}
}
@@ -108,11 +110,11 @@ class AppCompatCameraPolicy {
}
}
- boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
- }
- return false;
+ static boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(activity);
}
/**
@@ -125,11 +127,11 @@ class AppCompatCameraPolicy {
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
- boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
- }
- return false;
+ static boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(activity);
}
void start() {
@@ -176,23 +178,31 @@ class AppCompatCameraPolicy {
}
// TODO(b/369070416): have policies implement the same interface.
- boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(
- activity))
- || (mCameraCompatFreeformPolicy != null
- && mCameraCompatFreeformPolicy.shouldCameraCompatControlOrientation(
- activity));
+ static boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlOrientation(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlOrientation(activity));
}
// TODO(b/369070416): have policies implement the same interface.
- boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.shouldCameraCompatControlAspectRatio(
- activity))
- || (mCameraCompatFreeformPolicy != null
- && mCameraCompatFreeformPolicy.shouldCameraCompatControlAspectRatio(
- activity));
+ static boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .shouldCameraCompatControlAspectRatio(activity))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .shouldCameraCompatControlAspectRatio(activity));
}
// TODO(b/369070416): have policies implement the same interface.
@@ -200,29 +210,41 @@ class AppCompatCameraPolicy {
* @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and
* any camera compat treatment could be triggered for the current windowing mode.
*/
- private boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return (mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.isCameraRunningAndWindowingModeEligible(activity,
- /* mustBeFullscreen */ true))
- || (mCameraCompatFreeformPolicy != null && mCameraCompatFreeformPolicy
- .isCameraRunningAndWindowingModeEligible(activity));
+ private static boolean isCameraRunningAndWindowingModeEligible(
+ @NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return false;
+ }
+ return (cameraPolicy.mDisplayRotationCompatPolicy != null
+ && cameraPolicy.mDisplayRotationCompatPolicy
+ .isCameraRunningAndWindowingModeEligible(activity,
+ /* mustBeFullscreen */ true))
+ || (cameraPolicy.mCameraCompatFreeformPolicy != null
+ && cameraPolicy.mCameraCompatFreeformPolicy
+ .isCameraRunningAndWindowingModeEligible(activity));
}
@Nullable
String getSummaryForDisplayRotationHistoryRecord() {
- if (mDisplayRotationCompatPolicy != null) {
- return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
- }
- return null;
+ return mDisplayRotationCompatPolicy != null
+ ? mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord()
+ : null;
}
// TODO(b/369070416): have policies implement the same interface.
- float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
- float displayRotationCompatPolicyAspectRatio = mDisplayRotationCompatPolicy != null
- ? mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
+ static float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+ final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity);
+ if (cameraPolicy == null) {
+ return 1.0f;
+ }
+ float displayRotationCompatPolicyAspectRatio =
+ cameraPolicy.mDisplayRotationCompatPolicy != null
+ ? cameraPolicy.mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
: MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
- float cameraCompatFreeformPolicyAspectRatio = mCameraCompatFreeformPolicy != null
- ? mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
+ float cameraCompatFreeformPolicyAspectRatio =
+ cameraPolicy.mCameraCompatFreeformPolicy != null
+ ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
: MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
return Math.max(displayRotationCompatPolicyAspectRatio,
cameraCompatFreeformPolicyAspectRatio);
@@ -232,8 +254,8 @@ class AppCompatCameraPolicy {
* Whether we should apply the min aspect ratio per-app override only when an app is connected
* to the camera.
*/
- boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
- return isCameraRunningAndWindowingModeEligible(activityRecord)
+ static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
+ return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
&& activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 5bd4aeb64b90..f5d58eac1113 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -58,10 +58,8 @@ class AppCompatOrientationPolicy {
&& displayContent.getIgnoreOrientationRequest();
final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
.getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
- .getAppCompatCameraPolicy(mActivityRecord);
- final boolean shouldCameraCompatControlOrientation = cameraPolicy != null
- && cameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
+ final boolean shouldCameraCompatControlOrientation =
+ AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
@@ -98,7 +96,7 @@ class AppCompatOrientationPolicy {
if (displayContent != null
&& mAppCompatOverrides.getAppCompatCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
- && !displayContent.mAppCompatCameraPolicy
+ && !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
return candidate;
}
@@ -213,5 +211,4 @@ class AppCompatOrientationPolicy {
}
return false;
}
-
}
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 1073713cca52..264c8beb44bf 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -146,7 +146,7 @@ class BackgroundLaunchProcessController {
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
- if (checkConfiguration.checkVisibility && hasActivityInVisibleTask
+ if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
&& appSwitchState != APP_SWITCH_DISALLOW) {
return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
"process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 3b2f723fb172..c8cb62132b4c 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -194,12 +194,8 @@ public class DesktopAppCompatAspectRatioPolicy {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
- mActivityRecord);
- final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
- && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
- && !shouldOverrideMinAspectRatioForCamera) {
+ && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
if (mActivityRecord.isUniversalResizeable()) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index a74b0063588a..4824c1613f53 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -21,6 +21,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Log;
import android.view.Surface;
@@ -128,7 +129,7 @@ class Dimmer {
/**
* Set the parameters to prepare the dim to be relative parented to the dimming container
*/
- void prepareReparent(@NonNull WindowContainer<?> geometryParent,
+ void prepareReparent(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState relativeParent) {
mAnimationHelper.setRequestedRelativeParent(relativeParent);
mAnimationHelper.setRequestedGeometryParent(geometryParent);
@@ -221,7 +222,7 @@ class Dimmer {
* @param dimmingContainer The container that is dimming. The dim layer will be rel-z
* parented below it
*/
- public void adjustPosition(@NonNull WindowContainer<?> geometryParent,
+ public void adjustPosition(@Nullable WindowContainer<?> geometryParent,
@NonNull WindowState dimmingContainer) {
if (mDimState != null) {
mDimState.prepareReparent(geometryParent, dimmingContainer);
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 298edaeaa6d2..3999e03c8a53 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -108,7 +108,7 @@ public class DimmerAnimationHelper {
}
// Sets the requested layer to reparent the dim to without applying it immediately
- void setRequestedGeometryParent(WindowContainer<?> geometryParent) {
+ void setRequestedGeometryParent(@Nullable WindowContainer<?> geometryParent) {
if (geometryParent != null) {
mRequestedProperties.mGeometryParent = geometryParent;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b414a862f874..61b13a8c97cb 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -47,6 +47,7 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
@@ -412,6 +413,22 @@ class InsetsPolicy {
state.addSource(imeSource);
return state;
}
+ } else if (Flags.refactorInsetsController()
+ && (w.mMergedExcludeInsetsTypes & WindowInsets.Type.ime()) != 0) {
+ // In some cases (e.g. split screen from when the IME was requested and the animation
+ // actually starts) the insets should not be send, unless the flag is unset.
+ final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+ if (originalImeSource != null && originalImeSource.isVisible()) {
+ final InsetsState state = copyState
+ ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource imeSource = new InsetsSource(originalImeSource);
+ // Setting the height to zero, pretending we're in floating mode
+ imeSource.setFrame(0, 0, 0, 0);
+ imeSource.setVisibleFrame(imeSource.getFrame());
+ state.addSource(imeSource);
+ return state;
+ }
}
return originalState;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ede587c9e64b..5dddf36a8d8b 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -458,6 +458,12 @@ class InsetsStateController {
mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
}
+ void notifyInsetsChanged(ArraySet<WindowState> changedWindows) {
+ for (int i = changedWindows.size() - 1; i >= 0; i--) {
+ mDispatchInsetsChanged.accept(changedWindows.valueAt(i));
+ }
+ }
+
/**
* Checks if the control target has pending controls.
*
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fde502f2306c..188b368c47c5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1947,7 +1947,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildCleanupTransaction(mCleanupTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
- mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
+ mController.dispatchLegacyAppTransitionStarting(participantDisplays,
+ mStatusBarTransitionDelay);
try {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 37c3ce80f670..b7fe32713100 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1356,12 +1356,13 @@ class TransitionController {
}
}
- void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
+ void dispatchLegacyAppTransitionStarting(DisplayContent[] participantDisplays,
+ long statusBarTransitionDelay) {
final long now = SystemClock.uptimeMillis();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i);
- for (int j = 0; j < info.getRootCount(); ++j) {
- final int displayId = info.getRoot(j).getDisplayId();
+ for (int j = 0; j < participantDisplays.length; ++j) {
+ final int displayId = participantDisplays[j].mDisplayId;
if (shouldDispatchLegacyListener(listener, displayId)) {
listener.onAppTransitionStartingLocked(
now + statusBarTransitionDelay,
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 8e32813f7ecc..242883612124 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -156,7 +156,7 @@ public class TrustedPresentationListenerController {
Listeners mRegisteredListeners = new Listeners();
- private InputWindowHandle[] mLastWindowHandles;
+ private Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> mLastWindowHandles;
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
@@ -222,10 +222,10 @@ public class TrustedPresentationListenerController {
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> computeTpl(windowHandles));
+ mHandler.post(() -> computeTpl(new Pair<>(windowHandles, displayInfos)));
}
};
- mLastWindowHandles = mWindowInfosListener.register().first;
+ mLastWindowHandles = mWindowInfosListener.register();
}
private void unregisterWindowInfosListener() {
@@ -238,28 +238,52 @@ public class TrustedPresentationListenerController {
mLastWindowHandles = null;
}
- private void computeTpl(InputWindowHandle[] windowHandles) {
+ private void computeTpl(
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> windowHandles) {
mLastWindowHandles = windowHandles;
- if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+ if (mLastWindowHandles == null || mLastWindowHandles.first.length == 0
|| mRegisteredListeners.isEmpty()) {
return;
}
Rect tmpRect = new Rect();
+ RectF tmpRectF = new RectF();
+ Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
Region coveredRegionsAbove = new Region();
long currTimeMs = System.currentTimeMillis();
- ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+ ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- for (var windowHandle : mLastWindowHandles) {
+ for (var windowHandle : mLastWindowHandles.first) {
if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- tmpRect.set(windowHandle.frame);
+ var displayFound = false;
+ tmpRectF.set(windowHandle.frame);
+ for (var displayHandle : mLastWindowHandles.second) {
+ if (displayHandle.mDisplayId == windowHandle.displayId) {
+ // Transform the window frame into display logical space and then
+ // crop by the logical display size
+ displayHandle.mTransform.mapRect(tmpRectF);
+ tmpRectF.round(tmpRect);
+ tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
+ displayHandle.mLogicalSize.getHeight());
+ tmpRect.intersect(tmpLogicalDisplaySize);
+ displayFound = true;
+ break;
+ }
+ }
+
+ if (!displayFound) {
+ ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
+ windowHandle.displayId);
+ continue;
+ }
+
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da64a5f07aeb..af57c8422881 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -32,6 +32,7 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
@@ -173,6 +174,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
+ /**
+ * The combined excluded insets types (combined mExcludeInsetsTypes and the
+ * mMergedExcludeInsetsTypes from its parent)
+ */
+ protected @InsetsType int mMergedExcludeInsetsTypes = 0;
+ private @InsetsType int mExcludeInsetsTypes = 0;
+
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
@@ -555,6 +563,49 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return mControllableInsetProvider;
}
+ /**
+ * Sets the excludeInsetsTypes of this window and updates the mMergedExcludeInsetsTypes of
+ * all child nodes in the hierarchy.
+ *
+ * @param excludeInsetsTypes the excluded {@link InsetsType} that should be set on this
+ * WindowContainer
+ */
+ void setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
+ if (excludeInsetsTypes == mExcludeInsetsTypes) {
+ return;
+ }
+ mExcludeInsetsTypes = excludeInsetsTypes;
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
+ }
+
+ private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
+ @InsetsType int excludeInsetsTypesFromParent) {
+ final ArraySet<WindowState> changedWindows = new ArraySet<>();
+ updateMergedExcludeInsetsTypes(excludeInsetsTypesFromParent, changedWindows);
+ if (getDisplayContent() != null) {
+ getDisplayContent().getInsetsStateController().notifyInsetsChanged(changedWindows);
+ }
+ }
+
+ private void updateMergedExcludeInsetsTypes(
+ @InsetsType int excludeInsetsTypesFromParent, ArraySet<WindowState> changedWindows) {
+ final int newMergedExcludeInsetsTypes = mExcludeInsetsTypes | excludeInsetsTypesFromParent;
+ if (newMergedExcludeInsetsTypes == mMergedExcludeInsetsTypes) {
+ return;
+ }
+ mMergedExcludeInsetsTypes = newMergedExcludeInsetsTypes;
+
+ final WindowState win = asWindowState();
+ if (win != null) {
+ changedWindows.add(win);
+ }
+ // Apply to all children
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowContainer<?> child = mChildren.get(i);
+ child.updateMergedExcludeInsetsTypes(mMergedExcludeInsetsTypes, changedWindows);
+ }
+ }
@Override
final protected WindowContainer getParent() {
@@ -653,6 +704,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(0);
return;
}
@@ -667,6 +719,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// new parent.
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
+ mergeExcludeInsetsTypesAndNotifyInsetsChanged(mParent.mMergedExcludeInsetsTypes);
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e1e471479393..ebf645d84f95 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6810,30 +6810,36 @@ public class WindowManagerService extends IWindowManager.Stub
* @param logLevel Determines the amount of data to be written to the Protobuf.
*/
void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
- mPolicy.dumpDebug(proto, POLICY);
- mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
- final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
- if (topFocusedDisplayContent.mCurrentFocus != null) {
- topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
- }
- if (topFocusedDisplayContent.mFocusedApp != null) {
- topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
- }
- final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
- 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);
-
- // This is always true for now since we still update the window frames at the server side.
- // Once we move the window layout to the client side, this can be false when we are waiting
- // for the frames.
- proto.write(WINDOW_FRAMES_VALID, true);
-
- // Write the BackNavigationController's state into the protocol buffer
- mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
+ try {
+ mPolicy.dumpDebug(proto, POLICY);
+ mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
+ final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
+ if (topFocusedDisplayContent.mCurrentFocus != null) {
+ topFocusedDisplayContent.mCurrentFocus
+ .writeIdentifierToProto(proto, FOCUSED_WINDOW);
+ }
+ if (topFocusedDisplayContent.mFocusedApp != null) {
+ topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+ }
+ final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+ 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);
+
+ // This is always true for now since we still update the window frames at the server
+ // side. Once we move the window layout to the client side, this can be false when we
+ // are waiting for the frames.
+ proto.write(WINDOW_FRAMES_VALID, true);
+
+ // Write the BackNavigationController's state into the protocol buffer
+ mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
}
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 09a2bf9647e9..f8d0bc252b0f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -67,6 +67,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
@@ -1449,6 +1450,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
break;
}
+ case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
+ break;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 08789122c607..66f9230098d8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5215,7 +5215,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// but not window manager visible (!isVisibleNow()), it can still be the parent of the
// dim, but can not create a new surface or continue a dim alone.
Dimmer dimmer;
- WindowContainer<?> geometryParent = task;
+ WindowContainer<?> geometryParent = null;
if (Flags.useTasksDimOnly()) {
geometryParent = getDimParent();
dimmer = getDimController();
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index fe267261b9e6..68834370c191 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -167,12 +167,7 @@ abstract class WindowTracing {
long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
- try {
- mService.dumpDebugLocked(os, logLevel);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
+ mService.dumpDebugLocked(os, logLevel);
}
os.end(token);
} catch (Exception e) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index a07facf79423..776de2e52061 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -712,6 +712,12 @@
minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
+ <!-- The time after which the stylus is to be assumed to be not under use. This will
+ enable the logic of changing the brightness with ambient light changes -->
+ <xs:element name="idleStylusTimeoutMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 5309263ed87c..110a5a20da6a 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,12 +8,14 @@ package com.android.server.display.config {
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
+ method public final java.math.BigInteger getIdleStylusTimeoutMillis();
method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
+ method public final void setIdleStylusTimeoutMillis(java.math.BigInteger);
}
public enum AutoBrightnessModeName {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4e89b85305d1..2be999fc84e0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8031,8 +8031,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
- calledOnParentInstance, factoryReset);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId, factoryReset);
}
private String getGenericWipeReason(
@@ -8188,17 +8187,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* factory reset
*/
private void wipeDataNoLock(@Nullable ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId, boolean calledOnParentInstance,
- @Nullable Boolean factoryReset) {
+ String wipeReasonForUser, int userId, @Nullable Boolean factoryReset) {
wtfIfInLock();
final String adminPackage;
if (admin != null) {
adminPackage = admin.getPackageName();
} else {
- int callerId = mInjector.binderGetCallingUid();
- String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerId);
+ int callerUid = mInjector.binderGetCallingUid();
+ String[] adminPackages = mInjector.getPackageManager().getPackagesForUid(callerUid);
Preconditions.checkState(adminPackages.length > 0,
- "Caller %s does not have any associated packages", callerId);
+ "Caller %s does not have any associated packages", callerUid);
adminPackage = adminPackages[0];
}
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -8220,32 +8218,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
- });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean isMainUser = userId == getMainUserId();
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
- } else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean isMainUser = userId == getMainUserId();
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
} else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- Preconditions.checkCallAuthorization(!isSystemUser,
+ // Explicit behaviour
+ if (factoryReset) {
+ wipeDevice = true;
+ } else {
+ Preconditions.checkState(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
&& mUserManager.getAliveUsers().stream()
@@ -8253,13 +8241,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.noneMatch(UserInfo::isFull);
Preconditions.checkState(!isLastNonHeadlessUser,
"Removing user %s would leave the device without any active users. "
- + "Consider factory resetting the device instead.",
- userId);
- });
- wipeDevice = false;
+ + "Consider factory resetting the device instead.", userId);
+ wipeDevice = false;
+ }
}
- }
- mInjector.binderWithCleanCallingIdentity(() -> {
+
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -8600,7 +8586,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
userId,
- /* calledOnParentInstance= */ parent,
// factoryReset=null to enable U- behaviour
/* factoryReset= */ null);
} catch (SecurityException e) {
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7d5532f6e401..5c4716dc751e 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -57,7 +57,6 @@ import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doReturn
@@ -384,10 +383,6 @@ class PackageManagerComponentLabelIconOverrideTest {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
PackageManager.PERMISSION_GRANTED
}
- whenever(this.checkPermission(
- eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) {
- PackageManager.PERMISSION_GRANTED
- }
}
val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
whenever(this.snapshot()) { this@mock }
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca7775e22..70a2d4847ce7 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -45,7 +45,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.nene.users.UserReference;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.TestUtils;
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index 9560ec9990ad..c841643c6654 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -36,6 +36,7 @@ android_test {
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
+ "kotlin-test",
"platform-test-annotations",
"services.appfunctions",
"servicestests-core-utils",
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
index da3e94f64e56..d3262046cad8 100644
--- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
+++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt
@@ -15,8 +15,12 @@
*/
package android.app.appfunctions
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED
+import android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED
import android.app.appsearch.AppSearchSchema
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -108,32 +112,43 @@ class AppFunctionRuntimeMetadataTest {
assertThat(runtimeMetadata.packageName).isEqualTo("com.pkg")
assertThat(runtimeMetadata.functionId).isEqualTo("funcId")
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId)
.isEqualTo("android\$apps-db/app_functions#com.pkg/funcId")
}
@Test
- fun setEnabled_true() {
+ fun setEnabled_enabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(APP_FUNCTION_STATE_ENABLED).build()
- assertThat(runtimeMetadata.enabled).isTrue()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_ENABLED)
}
@Test
- fun setEnabled_false() {
+ fun setEnabled_disabled() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DISABLED).build()
- assertThat(runtimeMetadata.enabled).isFalse()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DISABLED)
}
@Test
- fun setEnabled_null() {
+ fun setEnabled_default() {
val runtimeMetadata =
- AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build()
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(
+ APP_FUNCTION_STATE_DEFAULT).build()
- assertThat(runtimeMetadata.enabled).isNull()
+ assertThat(runtimeMetadata.enabled).isEqualTo(APP_FUNCTION_STATE_DEFAULT)
+ }
+
+ @Test
+ fun setEnabled_illegalArgument() {
+ val runtimeMetadataBuilder =
+ AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId")
+ assertFailsWith<IllegalArgumentException>("Value of EnabledState is unsupported.") {
+ runtimeMetadataBuilder.setEnabled(-1)
+ }
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 8e1be9a777fd..3976ea4fc86e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -157,6 +157,7 @@ public final class DisplayDeviceConfigTest {
.getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ assertEquals(0, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -253,6 +254,7 @@ public final class DisplayDeviceConfigTest {
.getLux().intValue());
assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
.getTimeout().intValue());
+ assertEquals(1000, mDisplayDeviceConfig.getIdleStylusTimeoutMillis());
}
@Test
@@ -1479,6 +1481,7 @@ public final class DisplayDeviceConfigTest {
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + "<idleStylusTimeoutMillis>1000</idleStylusTimeoutMillis>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
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 342c87a6b5f6..6093a67cd320 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1309,6 +1309,38 @@ public class DisplayManagerServiceTest {
}
/**
+ * Tests that it's not allowed to create an auto-mirror virtual display without
+ * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays
+ */
+ @Test
+ public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException()
+ throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
* Tests that the virtual display is added to the default display group when created with
* VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
*/
@@ -1320,6 +1352,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1352,6 +1385,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1418,6 +1452,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1453,6 +1488,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index e863f1574932..e678acc092e9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -39,6 +39,7 @@ import android.content.pm.PackageManagerInternal;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Parcel;
import android.os.Process;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
@@ -580,6 +581,50 @@ public class ApplicationStartInfoTest {
assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
}
+ /**
+ * Test to confirm that parcel read and write implementations match, correctly loading records
+ * with the same values and leaving no data unread.
+ */
+ @Test
+ public void testParcelReadWriteMatch() throws Exception {
+ // Create a start info records with all fields set.
+ ApplicationStartInfo startInfo = new ApplicationStartInfo(1234L);
+ startInfo.setPid(123);
+ startInfo.setRealUid(987);
+ startInfo.setPackageUid(654);
+ startInfo.setDefiningUid(321);
+ startInfo.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+ startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+ startInfo.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+ startInfo.setLaunchMode(ApplicationStartInfo.LAUNCH_MODE_SINGLE_TOP);
+ startInfo.setPackageName(APP_1_PACKAGE_NAME);
+ startInfo.setProcessName(APP_1_PROCESS_NAME);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, 999L);
+ startInfo.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME, 888L);
+ startInfo.setForceStopped(true);
+ startInfo.setStartComponent(ApplicationStartInfo.START_COMPONENT_OTHER);
+ startInfo.setIntent(buildIntent(COMPONENT));
+
+ // Write the start info to a parcel.
+ Parcel parcel = Parcel.obtain();
+ startInfo.writeToParcel(parcel, 0 /* flags */);
+
+ // Set the data position back to 0 so it's ready to be read.
+ parcel.setDataPosition(0);
+
+ // Now load the record from the parcel.
+ ApplicationStartInfo startInfoFromParcel = new ApplicationStartInfo(parcel);
+
+ // Make sure there is no unread data remaining in the parcel, and confirm that the loaded
+ // start info object is equal to the one it was written from. Check dataAvail first as if
+ // that check fails then the next check will fail too, but knowing the status of this check
+ // will tell us that we're missing a read or write. Check the objects are equals second as
+ // if the avail check passes and equals fails, then we know we're reading all the data just
+ // not to the correct fields.
+ assertEquals(0, parcel.dataAvail());
+ assertTrue(startInfo.equals(startInfoFromParcel));
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 24e7242792fb..54ee2a3ae0d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -33,6 +33,7 @@ import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Process;
+import android.os.UserHandle;
import android.util.SparseArray;
import org.junit.After;
@@ -340,6 +341,24 @@ public class PackageMonitorCallbackHelperTest {
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
+ @Test
+ public void registerPackageMonitor_callbackNotInAllowListSystemUidSecondUser_callbackIsCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ int userId = 10;
+ int fakeAppId = 12345;
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(userId, new int[]{UserHandle.getUid(userId, fakeAppId)});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId,
+ UserHandle.getUid(userId, Process.SYSTEM_UID));
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{userId},
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cbe6700f4d41..6ede334aec80 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -230,6 +230,20 @@ java_library {
}
java_library {
+ name: "servicestests-utils-ravenwood",
+ srcs: [
+ "utils/**/*.java",
+ "utils/**/*.kt",
+ "utils-mockito/**/*.kt",
+ ],
+ libs: [
+ "android.test.runner.stubs.system",
+ "junit",
+ "mockito-ravenwood-prebuilt",
+ ],
+}
+
+java_library {
name: "mockito-test-utils",
srcs: [
"utils-mockito/**/*.kt",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 1426d5d20419..c4b4afd13a60 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -69,6 +69,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
@@ -92,6 +93,8 @@ import org.mockito.stubbing.Answer;
import org.testng.Assert;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
@RunWith(AndroidJUnit4.class)
public class FullScreenMagnificationControllerTest {
@@ -1440,19 +1443,42 @@ public class FullScreenMagnificationControllerTest {
@Test
public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ register(TEST_DISPLAY);
final float persistedScale =
mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY);
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- mFullScreenMagnificationController.setScale(DISPLAY_0, 1.0f, pivotPoint.x, pivotPoint.y,
+ mFullScreenMagnificationController.setScale(TEST_DISPLAY, 1.0f, pivotPoint.x, pivotPoint.y,
false, SERVICE_ID_1);
mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
persistedScale);
}
@Test
+ public void persistScale_setValuesOnMultipleDisplays() {
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ final PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, 3.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_1, 4.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(DISPLAY_1);
+
+ // persistScale may post a task to a background thread. Let's wait for it completes.
+ waitForBackgroundThread();
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_0),
+ 3.0f);
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(DISPLAY_1),
+ 4.0f);
+ }
+
+ @Test
public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
setScaleToMagnifying();
@@ -1494,6 +1520,15 @@ public class FullScreenMagnificationControllerTest {
);
}
+ private static void waitForBackgroundThread() {
+ final CompletableFuture<Void> future = new CompletableFuture<>();
+ BackgroundThread.getHandler().post(() -> future.complete(null));
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException ignore) {
+ }
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 87fe6cf8f283..6aa8a32dd7db 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -300,7 +300,8 @@ public class MagnificationConnectionManagerTest {
mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
- mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f);
+ mMagnificationConnectionManager.setScale(TEST_DISPLAY,
+ MagnificationScaleProvider.MAX_SCALE * 2.f);
assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY),
MagnificationScaleProvider.MAX_SCALE);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index efc2d974a7cc..1bea371c5786 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -217,7 +217,13 @@ public class UtilsTest {
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
- assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.MANDATORY_BIOMETRICS));
+ if (Flags.mandatoryBiometrics()) {
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext,
+ Authenticators.MANDATORY_BIOMETRICS));
+ } else {
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext,
+ Authenticators.MANDATORY_BIOMETRICS));
+ }
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index ee63d5d32ff1..425bb158f997 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -33,6 +33,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,11 +52,15 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
+import android.app.Instrumentation;
import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -68,6 +73,7 @@ import android.media.projection.ReviewGrantedConsentResult;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.TestLooper;
@@ -88,6 +94,7 @@ import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -98,6 +105,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -312,7 +320,6 @@ public class MediaProjectionManagerServiceTest {
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
- @SuppressLint("MissingPermission")
@EnableFlags(android.companion.virtualdevice.flags
.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
@Test
@@ -335,6 +342,36 @@ public class MediaProjectionManagerServiceTest {
assertThat(mService.getActiveProjectionInfo()).isNotNull();
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_RoleHeld() {
+ runWithRole(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, () -> {
+ try {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ MediaProjectionManagerService.MediaProjection projection =
+ mService.createProjectionInternal(Process.myUid(),
+ mContext.getPackageName(),
+ TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_DENIED).when(
+ mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertWithMessage("Failed to run projection")
+ .that(mService.getActiveProjectionInfo()).isNotNull();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
@@ -1202,6 +1239,47 @@ public class MediaProjectionManagerServiceTest {
return mService.getProjectionInternal(UID, PACKAGE_NAME);
}
+ /**
+ * Run the provided block giving the current context's package the provided role.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void runWithRole(String role, Runnable block) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ String packageName = mContext.getPackageName();
+ UserHandle user = instrumentation.getTargetContext().getUser();
+ RoleManager roleManager = Objects.requireNonNull(
+ mContext.getSystemService(RoleManager.class));
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+ roleManager.setBypassingRoleQualification(true);
+ roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ mContext.getMainExecutor(), success -> {
+ if (success) {
+ latch.countDown();
+ } else {
+ Assert.fail("Couldn't set role for test (failure) " + role);
+ }
+ });
+ assertWithMessage("Couldn't set role for test (timeout) : " + role)
+ .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+ block.run();
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
+ roleManager.setBypassingRoleQualification(false);
+ instrumentation.getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
CountDownLatch mLatch = new CountDownLatch(1);
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 585df84f7f90..22a4f85758eb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2662,6 +2662,17 @@ public class GroupHelperTest extends UiServiceTestCase {
when(n.isColorized()).thenReturn(true);
when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+
+ NotificationRecord notification_media = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ n = mock(Notification.class);
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_media.isConversation()).thenReturn(false);
+ when(notification_media.getNotification()).thenReturn(n);
+ when(notification_media.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isMediaNotification()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_media)).isNull();
}
@Test
@@ -2756,7 +2767,7 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
public void testNonGroupableNotifications_forceGroupConversations() {
- // Check that there is no valid section for: calls, foreground services
+ // Check that there is no valid section for: calls, foreground services, media notifications
NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
"", false, IMPORTANCE_LOW));
Notification n = mock(Notification.class);
@@ -2780,6 +2791,17 @@ public class GroupHelperTest extends UiServiceTestCase {
when(n.isColorized()).thenReturn(true);
when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+
+ NotificationRecord notification_media = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ n = mock(Notification.class);
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_media.isConversation()).thenReturn(false);
+ when(notification_media.getNotification()).thenReturn(n);
+ when(notification_media.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isMediaNotification()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_media)).isNull();
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 18ca09be235c..bf0586ceb32d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -18,11 +18,21 @@ package com.android.server.notification;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -30,12 +40,16 @@ import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Adjustment;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import com.android.server.UiServiceTestCase;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -43,6 +57,9 @@ import java.util.Objects;
public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testExtractsAdjustment() {
NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
@@ -111,6 +128,44 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
}
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_triggerRegrouping() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNotNull();
+ regroupingTask.applyChangesLocked(r);
+ verify(groupHelper, times(1)).onChannelUpdated(r);
+ }
+
+ @Test
+ @DisableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUPING})
+ public void testClassificationAdjustments_notTriggerRegrouping_flagsDisabled() {
+ GroupHelper groupHelper = mock(GroupHelper.class);
+ NotificationAdjustmentExtractor extractor = new NotificationAdjustmentExtractor();
+ extractor.setGroupHelper(groupHelper);
+
+ NotificationRecord r = generateRecord();
+
+ Bundle classificationAdj = new Bundle();
+ classificationAdj.putParcelable(Adjustment.KEY_TYPE, mock(NotificationChannel.class));
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), classificationAdj, "", 0);
+ r.addAdjustment(adjustment);
+
+ RankingReconsideration regroupingTask = extractor.process(r);
+ assertThat(regroupingTask).isNull();
+ }
+
private NotificationRecord generateRecord() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification.Builder builder = new Notification.Builder(getContext())
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index a45b102278ef..797b95b5dbe9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -15,12 +15,15 @@
*/
package com.android.server.notification;
+import static android.os.UserHandle.USER_ALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +36,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.content.ComponentName;
@@ -42,24 +46,28 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.Adjustment;
import android.testing.TestableContext;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Xml;
-import android.Manifest;
+
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -71,8 +79,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+@RunWith(AndroidJUnit4.class)
public class NotificationAssistantsTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private PackageManager mPm;
@Mock
@@ -98,6 +110,35 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
ComponentName mCn = new ComponentName("a", "b");
+
+ // Helper function to hold mApproved lock, avoid GuardedBy lint errors
+ private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) {
+ synchronized (assistant.mApproved) {
+ return assistant.mUserSetServices.get(userId).isEmpty();
+ }
+ }
+
+ private void writeXmlAndReload(int userId) throws Exception {
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mAssistants.writeXml(serializer, false, userId);
+ serializer.endDocument();
+ serializer.flush();
+
+ //fail(baos.toString("UTF-8"));
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+
+ parser.nextTag();
+ mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
+ }
+
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -164,25 +205,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
true);
- TypedXmlSerializer serializer = Xml.newFastSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
- serializer.startDocument(null, true);
- mAssistants.writeXml(serializer, true, userId);
- serializer.endDocument();
- serializer.flush();
-
- //fail(baos.toString("UTF-8"));
-
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
-
- parser.nextTag();
- mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ writeXmlAndReload(USER_ALL);
ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
// approved should not be null
@@ -203,11 +226,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
@@ -226,11 +247,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, true,
+ mAssistants.readXml(parser, mNm::canUseManagedServices, true,
ActivityManager.getCurrentUser());
ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
@@ -253,11 +272,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(mAssistants.mIsUserChanged.get(0));
@@ -273,11 +290,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(0)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -294,11 +309,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(0)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -314,11 +327,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -334,11 +345,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
final TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
- TriPredicate<String, Integer, String> allowedManagedServicePackages =
- mNm::canUseManagedServices;
parser.nextTag();
- mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
verify(mAssistants, times(1)).upgradeUserSet();
assertTrue(isUserSetServicesEmpty(mAssistants, 0));
@@ -361,7 +370,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
new ByteArrayInputStream(xml.toString().getBytes())), null);
parser.nextTag();
- mAssistants.readXml(parser, null, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, null, false, USER_ALL);
assertEquals(1, mAssistants.getAllowedComponents(0).size());
assertEquals(new ArrayList(Arrays.asList(new ComponentName("a", "a"))),
@@ -378,7 +387,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.toString().getBytes())), null);
parser.nextTag();
- mAssistants.readXml(parser, null, false, UserHandle.USER_ALL);
+ mAssistants.readXml(parser, null, false, USER_ALL);
verify(mNm, never()).setDefaultAssistantForUser(anyInt());
verify(mAssistants, times(1)).addApprovedList(
@@ -529,10 +538,66 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
assertEquals(new ArraySet<>(), mAssistants.getDefaultComponents());
}
- // Helper function to hold mApproved lock, avoid GuardedBy lint errors
- private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) {
- synchronized (assistant.mApproved) {
- return assistant.mUserSetServices.get(userId).isEmpty();
- }
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
+
+ ManagedServices.ManagedServiceInfo info =
+ mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
+ mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId)).contains(
+ Adjustment.KEY_NOT_CONVERSATION);
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(1);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ ManagedServices.ManagedServiceInfo info =
+ mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
+ mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId)).contains(
+ Adjustment.KEY_NOT_CONVERSATION);
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(1);
+ }
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+
+ writeXmlAndReload(USER_ALL);
+
+ assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
}
-}
+} \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 45cd5719cd86..03cad24a738a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2637,6 +2637,63 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_msgCategory() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ // Create a conversation group with GROUP_ALERT_SUMMARY behavior
+ // Where the summary is not MessagingStyle
+ final String groupKey = "grup_name";
+ final String shortcutId = "shortcut";
+ NotificationRecord summary = getBeepyNotificationRecord(groupKey, GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ summary.getNotification().category = Notification.CATEGORY_MESSAGE;
+ ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
+ summary.setShortcutInfo(sb.setId(shortcutId).build());
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // Post child notifications with GROUP_ALERT_SUMMARY
+ NotificationRecord child = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, groupKey, Notification.GROUP_ALERT_SUMMARY, false, mUser, mPkg,
+ shortcutId);
+
+ // Should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 2nd update for summary should beep at 50% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // 3rd update for summary should not beep
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
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 bbf2cbdbc145..3c120e1ada2a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -33,6 +33,7 @@ import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
@@ -2872,6 +2873,131 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_forceGrouped_clearsSummaryFlag() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ // Old record was a summary and it was auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ mService.addNotification(r);
+ mService.convertSummaryToNotificationLocked(r.getKey());
+ mService.addAutogroupKeyLocked(r.getKey(), aggregateGroupName, true);
+
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_forceGroupedRegular_updatedAsSummary_clearsSummaryFlag()
+ throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ // Old record was not summary and it was auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ mService.addNotification(r);
+ mService.addAutogroupKeyLocked(r.getKey(), aggregateGroupName, true);
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testEnqueueNotification_notForceGrouped_dontClearSummaryFlag()
+ throws Exception {
+ final String originalGroupName = "originalGroup";
+
+ // Old record was a summary and it was not auto-grouped
+ final NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, true);
+ mService.addNotification(r);
+ assertThat(mService.mNotificationList).hasSize(1);
+
+ // Update record is a summary
+ final Notification updatedNotification = generateNotificationRecord(
+ mTestNotificationChannel, 0, originalGroupName, true).getNotification();
+ assertThat(updatedNotification.flags & FLAG_GROUP_SUMMARY).isEqualTo(FLAG_GROUP_SUMMARY);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), updatedNotification, r.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that FLAG_GROUP_SUMMARY was not removed
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(
+ FLAG_GROUP_SUMMARY);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testRemoveFGSFlagFromNotification_enqueued_forceGrouped_clearsSummaryFlag() {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null,
+ originalGroupName, true);
+ r.getSbn().getNotification().flags &= ~FLAG_GROUP_SUMMARY;
+ r.setOverrideGroupKey(aggregateGroupName);
+ mService.addEnqueuedNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ mPkg, r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ assertThat(mService.mEnqueuedNotifications).hasSize(1);
+ assertThat(mService.mEnqueuedNotifications.get(0).getFlags() & FLAG_GROUP_SUMMARY)
+ .isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testRemoveFGSFlagFromNotification_posted_forceGrouped_clearsSummaryFlag() {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null,
+ originalGroupName, true);
+ r.getSbn().getNotification().flags &= ~FLAG_GROUP_SUMMARY;
+ r.setOverrideGroupKey(aggregateGroupName);
+ mService.addNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ mPkg, r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getFlags() & FLAG_GROUP_SUMMARY).isEqualTo(0);
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
when(mAmi.applyForegroundServiceNotification(
any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 9a6e81865947..5d4382a6331c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -92,6 +92,7 @@ public class RankingHelperTest extends UiServiceTestCase {
@Mock ZenModeHelper mMockZenModeHelper;
@Mock RankingConfig mConfig;
@Mock Vibrator mVibrator;
+ @Mock GroupHelper mGroupHelper;
private NotificationManager.Policy mTestNotificationPolicy;
private Notification mNotiGroupGSortA;
@@ -157,7 +158,7 @@ public class RankingHelperTest extends UiServiceTestCase {
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()},
- mock(IPlatformCompat.class));
+ mock(IPlatformCompat.class), mGroupHelper);
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 91eb2edeee13..c6cc941ba1cd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -60,6 +60,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -69,6 +70,7 @@ import java.util.Set;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
@@ -127,7 +129,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.ZenRule.class, getZenRuleExemptFields());
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
@@ -145,6 +147,337 @@ public class ZenModeDiffTest extends UiServiceTestCase {
}
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNoChangeAddRemove() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{no changes}");
+
+ d = new ZenModeDiff.RuleDiff(r1, null);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{removed}");
+
+ d = new ZenModeDiff.RuleDiff(null, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{added}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toString() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testRuleDiff_toStringNullStartPolicy() throws Exception {
+ // Start with two identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields(), false);
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ // Create a ZenRule with ZenDeviceEffects and ZenPolicy as null.
+ r1.zenPolicy = null;
+ r1.zenDeviceEffects = null;
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertThat(d.toString()).isEqualTo("ZenRuleDiff{"
+ + "enabled:true->false, "
+ + "conditionOverride:2->1, "
+ + "name:string1->string2, "
+ + "zenMode:2->1, "
+ + "conditionId:null->, "
+ + "condition:null->Condition["
+ + "state=STATE_TRUE,"
+ + "id=hello:,"
+ + "summary=,"
+ + "line1=,"
+ + "line2=,"
+ + "icon=-1,"
+ + "source=SOURCE_UNKNOWN,"
+ + "flags=2], "
+ + "component:null->ComponentInfo{b/b}, "
+ + "configurationActivity:null->ComponentInfo{a/a}, "
+ + "id:string1->string2, "
+ + "creationTime:200->100, "
+ + "enabler:string1->string2, "
+ + "zenPolicy:ZenPolicyDiff{added}, "
+ + "modified:true->false, "
+ + "pkg:string1->string2, "
+ + "zenDeviceEffects:ZenDeviceEffectsDiff{added}, "
+ + "triggerDescription:string1->string2, "
+ + "type:2->1, "
+ + "allowManualInvocation:true->false, "
+ + "iconResName:string1->string2, "
+ + "legacySuppressedEffects:2->1}");
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder().build();
+
+ // Both sides same rule
+ ZenModeDiff.DeviceEffectsDiff dSame = new ZenModeDiff.DeviceEffectsDiff(effects, effects);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.DeviceEffectsDiff deleted = new ZenModeDiff.DeviceEffectsDiff(effects, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.DeviceEffectsDiff added = new ZenModeDiff.DeviceEffectsDiff(null, effects);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.DeviceEffectsDiff d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testDeviceEffectsDiff_toString() throws Exception {
+ // Ensure device effects toString is readable.
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+
+ ZenModeDiff.DeviceEffectsDiff d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{no changes}");
+
+ d = new ZenModeDiff.DeviceEffectsDiff(effects1, null);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{removed}");
+
+ d = new ZenModeDiff.DeviceEffectsDiff(null, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{added}");
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+
+ d = new ZenModeDiff.DeviceEffectsDiff(effects1, effects2);
+ assertThat(d.toString()).isEqualTo("ZenDeviceEffectsDiff{"
+ + "mGrayscale:true->false, "
+ + "mSuppressAmbientDisplay:true->false, "
+ + "mDimWallpaper:true->false, "
+ + "mNightMode:true->false, "
+ + "mDisableAutoBrightness:true->false, "
+ + "mDisableTapToWake:true->false, "
+ + "mDisableTiltToWake:true->false, "
+ + "mDisableTouch:true->false, "
+ + "mMinimizeRadioUsage:true->false, "
+ + "mMaximizeDoze:true->false, "
+ + "mExtraEffects:[effect1]->[effect2]}");
+ }
+
+
+ @Test
+ public void testPolicyDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenPolicy effects = new ZenPolicy.Builder().build();
+
+ // Both sides same rule
+ ZenModeDiff.PolicyDiff dSame = new ZenModeDiff.PolicyDiff(effects, effects);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.PolicyDiff deleted = new ZenModeDiff.PolicyDiff(effects, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.PolicyDiff added = new ZenModeDiff.PolicyDiff(null, effects);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testPolicyDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(ZenPolicy.class, Collections.emptySet(),
+ false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.PolicyDiff d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff.
+ // Because ZenPolicy consolidates priority category and visual effect fields in a list,
+ // we cannot use reflection on ZenPolicy to get the list of fields.
+ ArrayList<String> diffFields = new ArrayList<>();
+ Field[] fields = ZenModeDiff.PolicyDiff.class.getDeclaredFields();
+
+ for (Field field : fields) {
+ int m = field.getModifiers();
+ if (Modifier.isStatic(m) && Modifier.isFinal(m)) {
+ diffFields.add((String) field.get(policy1));
+ }
+ }
+
+ for (String name : diffFields) {
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testPolicyDiff_toString() throws Exception {
+ // Ensure device effects toString is readable.
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+
+ ZenModeDiff.PolicyDiff d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{no changes}");
+
+ d = new ZenModeDiff.PolicyDiff(policy1, null);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{removed}");
+
+ d = new ZenModeDiff.PolicyDiff(null, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{added}");
+
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenPolicy.class, Collections.emptySet() /*no exempt fields*/, false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom, expectedTo);
+
+ d = new ZenModeDiff.PolicyDiff(policy1, policy2);
+ assertThat(d.toString()).isEqualTo("ZenPolicyDiff{"
+ + "mPriorityCategories_Reminders:1->2, "
+ + "mPriorityCategories_Events:1->2, "
+ + "mPriorityCategories_Messages:1->2, "
+ + "mPriorityCategories_Calls:1->2, "
+ + "mPriorityCategories_RepeatCallers:1->2, "
+ + "mPriorityCategories_Alarms:1->2, "
+ + "mPriorityCategories_Media:1->2, "
+ + "mPriorityCategories_System:1->2, "
+ + "mPriorityCategories_Conversations:1->2, "
+ + "mVisualEffects_FullScreenIntent:1->2, "
+ + "mVisualEffects_Lights:1->2, "
+ + "mVisualEffects_Peek:1->2, "
+ + "mVisualEffects_StatusBar:1->2, "
+ + "mVisualEffects_Badge:1->2, "
+ + "mVisualEffects_Ambient:1->2, "
+ + "mVisualEffects_NotificationList:1->2, "
+ + "mPriorityMessages:2->1, "
+ + "mPriorityCalls:2->1, "
+ + "mConversationSenders:2->1, "
+ + "mAllowChannels:2->1}");
+ }
+
private static Set<String> getZenRuleExemptFields() {
// "Metadata" fields are never compared.
Set<String> exemptFields = new LinkedHashSet<>(
@@ -194,7 +527,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.class, getConfigExemptAndFlaggedFields());
+ ZenModeConfig.class, getConfigExemptAndFlaggedFields(), false);
generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
@@ -223,7 +556,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+ ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS, false);
generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
@@ -359,17 +692,23 @@ public class ZenModeDiffTest extends UiServiceTestCase {
// Get the fields on which we would want to check a diff. The requirements are: not final or/
// static (as these should/can never change), and not in a specific list that's exempted.
- private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames)
+ private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames,
+ boolean includeFinal)
throws SecurityException {
Field[] fields = c.getDeclaredFields();
ArrayList<Field> out = new ArrayList<>();
for (Field field : fields) {
// Check for exempt reasons
+ // Anything in provided exemptNames is skipped.
+ if (exemptNames.contains(field.getName())) {
+ continue;
+ }
int m = field.getModifiers();
- if (Modifier.isFinal(m)
- || Modifier.isStatic(m)
- || exemptNames.contains(field.getName())) {
+ if (Modifier.isStatic(m)) {
+ continue;
+ }
+ if (!includeFinal && Modifier.isFinal(m)) {
continue;
}
out.add(field);
@@ -377,6 +716,106 @@ public class ZenModeDiffTest extends UiServiceTestCase {
return out;
}
+ // Generate a set of diffs for two ZenPolicy objects. Store the results in the provided
+ // expectation maps.
+ private void generateFieldDiffsForZenPolicy(ZenPolicy a, ZenPolicy b, List<Field> fields,
+ ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+ throws Exception {
+ // Loop through fields for which we want to check diffs, set a diff and keep track of
+ // what we set.
+ for (Field f : fields) {
+ f.setAccessible(true);
+ // Just double-check also that the fields actually are for the class declared
+ assertEquals(f.getDeclaringClass(), a.getClass());
+ Class<?> t = f.getType();
+
+ if (int.class.equals(t)) {
+ // these will not be valid for arbitrary int enums, but should suffice for a diff.
+ f.setInt(a, 2);
+ expectedA.put(f.getName(), 2);
+ f.setInt(b, 1);
+ expectedB.put(f.getName(), 1);
+ } else if (List.class.equals(t)) {
+ // Fieds mPriorityCategories and mVisualEffects store multiple values and
+ // must be treated separately.
+ List<Integer> aList = (ArrayList<Integer>) f.get(a);
+ List<Integer> bList = (ArrayList<Integer>) f.get(b);
+ if (f.getName().equals("mPriorityCategories")) {
+ // PRIORITY_CATEGORY_REMINDERS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 0,
+ "mPriorityCategories_Reminders");
+ // PRIORITY_CATEGORY_EVENTS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 1,
+ "mPriorityCategories_Events");
+ // PRIORITY_CATEGORY_MESSAGES
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 2,
+ "mPriorityCategories_Messages");
+ // PRIORITY_CATEGORY_CALLS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 3,
+ "mPriorityCategories_Calls");
+ // PRIORITY_CATEGORY_REPEAT_CALLERS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 4,
+ "mPriorityCategories_RepeatCallers");
+ // PRIORITY_CATEGORY_ALARMS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 5,
+ "mPriorityCategories_Alarms");
+ // PRIORITY_CATEGORY_MEDIA
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 6,
+ "mPriorityCategories_Media");
+ // PRIORITY_CATEGORY_SYSTEM
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 7,
+ "mPriorityCategories_System");
+ // PRIORITY_CATEGORY_CONVERSATIONS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 8,
+ "mPriorityCategories_Conversations");
+ // Assert that we've set every PriorityCategory enum value.
+ assertThat(Collections.frequency(aList, ZenPolicy.STATE_ALLOW))
+ .isEqualTo(ZenPolicy.NUM_PRIORITY_CATEGORIES);
+ } else if (f.getName().equals("mVisualEffects")) {
+ // VISUAL_EFFECT_FULL_SCREEN_INTENT
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 0,
+ "mVisualEffects_FullScreenIntent");
+ // VISUAL_EFFECT_LIGHTS
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 1,
+ "mVisualEffects_Lights");
+ // VISUAL_EFFECT_PEEK
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 2,
+ "mVisualEffects_Peek");
+ // VISUAL_EFFECT_STATUS_BAR
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 3,
+ "mVisualEffects_StatusBar");
+ // VISUAL_EFFECT_BADGE
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 4,
+ "mVisualEffects_Badge");
+ // VISUAL_EFFECT_AMBIENT
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 5,
+ "mVisualEffects_Ambient");
+ // VISUAL_EFFECT_NOTIFICATION_LIST
+ setPolicyListValueDiff(aList, bList, expectedA, expectedB, 6,
+ "mVisualEffects_NotificationList");
+ // Assert that we've set every VisualeEffect enum value.
+ assertThat(Collections.frequency(aList, ZenPolicy.STATE_ALLOW))
+ .isEqualTo(ZenPolicy.NUM_VISUAL_EFFECTS);
+ } else {
+ // Any other lists that are added should be added to the diff.
+ fail("could not generate field diffs for policy list: " + f.getName());
+ }
+ }
+ }
+ }
+
+ // Helper function to create a diff in two list values at a given index, and record that
+ // diff's values in the associated expected maps under the provided field name.
+ private void setPolicyListValueDiff(List<Integer> aList, List<Integer> bList,
+ ArrayMap<String, Object> expectedA,
+ ArrayMap<String, Object> expectedB,
+ int index, String fieldName) {
+ aList.set(index, ZenPolicy.STATE_ALLOW);
+ expectedA.put(fieldName, ZenPolicy.STATE_ALLOW);
+ bList.set(index, ZenPolicy.STATE_DISALLOW);
+ expectedB.put(fieldName, ZenPolicy.STATE_DISALLOW);
+ }
+
// Generate a set of generic diffs for the specified two objects and the fields to generate
// diffs for, and store the results in the provided expectation maps to be able to check the
// output later.
@@ -420,6 +859,44 @@ public class ZenModeDiffTest extends UiServiceTestCase {
expectedA.put(f.getName(), "string1");
f.set(b, "string2");
expectedB.put(f.getName(), "string2");
+ } else if (Set.class.equals(t)) {
+ Set<String> aSet = Set.of("effect1");
+ Set<String> bSet = Set.of("effect2");
+ f.set(a, aSet);
+ expectedA.put(f.getName(), aSet);
+ f.set(b, bSet);
+ expectedB.put(f.getName(), bSet);
+ } else if (ZenDeviceEffects.class.equals(t)) {
+ // Recurse into generating field diffs for ZenDeviceEffects.
+ ZenDeviceEffects effects1 = new ZenDeviceEffects.Builder().build();
+ ZenDeviceEffects effects2 = new ZenDeviceEffects.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenDeviceEffects.class, Collections.emptySet() /*no exempt fields*/, true);
+ generateFieldDiffs(effects1, effects2, fieldsForDiff, expectedFrom, expectedTo);
+ f.set(a, effects1);
+ expectedA.put(f.getName(), effects1);
+ f.set(b, effects2);
+ expectedB.put(f.getName(), effects2);
+ } else if (ZenPolicy.class.equals(t)) {
+ // Recurse into generating field diffs for ZenPolicy.
+ ZenPolicy policy1 = new ZenPolicy.Builder().build();
+ ZenPolicy policy2 = new ZenPolicy.Builder().build();
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(ZenPolicy.class,
+ Collections.emptySet(), false);
+ generateFieldDiffsForZenPolicy(policy1, policy2, fieldsForDiff, expectedFrom,
+ expectedTo);
+ f.set(a, policy1);
+ expectedA.put(f.getName(), policy1);
+ f.set(b, policy2);
+ expectedB.put(f.getName(), policy2);
} else {
// catch-all for other types: have the field be "added"
f.set(a, null);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index b997f5d9a2a0..a49f5a89b11b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -26,8 +26,6 @@ import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSAT
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -57,7 +55,6 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.server.UiServiceTestCase;
@@ -188,49 +185,6 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
}
@Test
- public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
- - SUPPRESSED_EFFECT_STATUS_BAR, 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongId() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_yes_WrongPackage() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android2");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- }
-
- @Test
- public void testSuppressDNDInfo_no() {
- NotificationRecord r = getNotificationRecord();
- when(r.getSbn().getPackageName()).thenReturn("android");
- when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
- Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
-
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
- assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
- }
-
- @Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
when(r.getSbn().getPackageName()).thenReturn("bananas");
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 d4cba8d726fb..294027b10b26 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -169,7 +169,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.config.sysui.TestableFlagResolver;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto;
@@ -747,54 +746,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- public void testZenUpgradeNotification() {
- /**
- * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
- * notification on watches. So, assume that the device is not watch.
- */
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
-
- // shows zen upgrade notification if stored settings says to shows,
- // zen has not been updated, boot is completed
- // and we're setting zen mode on
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- assertEquals(0, Settings.Secure.getInt(mContentResolver,
- Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, -1));
- }
-
- @Test
- public void testNoZenUpgradeNotification() {
- // doesn't show upgrade notification if stored settings says don't show
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
- public void testNoZenUpgradeNotificationZenUpdated() {
- // doesn't show upgrade notification since zen was already updated
- Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
- mZenModeHelper.mIsSystemServicesReady = true;
- mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-
- verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
- eq(SystemMessage.NOTE_ZEN_UPGRADE), any());
- }
-
- @Test
public void testZenSetInternalRinger_AllPriorityNotificationSoundsMuted() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
@@ -3032,6 +2983,33 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void updateAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+ ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+ sleepingRule.enabled = false;
+ sleepingRule.userModifiedFields = 0;
+ sleepingRule.name = "ZZZZZZZ...";
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+ AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
+ .build();
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, futureBedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+ .containsExactly(sleepingRule.id, bedtimeRuleId);
+
+ AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
+ .setType(TYPE_BEDTIME)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(bedtimeRuleId, bedtime, ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void testSetManualZenMode() {
setupZenConfig();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index e83a4b22d9bf..7536f5f54ba2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -47,7 +47,6 @@ import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.Handler;
-import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
@@ -119,7 +118,6 @@ public class VibrationThreadTest {
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
- @Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
@Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
@@ -668,7 +666,7 @@ public class VibrationThreadTest {
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
HalVibration vibration = createVibration(CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
- vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -848,7 +846,7 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
- vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+ vibration.fillFallbacks(unused -> fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
@@ -954,7 +952,8 @@ public class VibrationThreadTest {
assertTrue(mThread.isRunningVibrationId(vibration.id));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -1575,7 +1574,8 @@ public class VibrationThreadTest {
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibration.id));
- mVibrationConductor.binderDied();
+ mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
@@ -1865,9 +1865,9 @@ public class VibrationThreadTest {
VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(usage)
.build();
- HalVibration vib = new HalVibration(mVibrationToken,
- CombinedVibration.createParallel(effect),
- new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ HalVibration vib = new HalVibration(
+ new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ CombinedVibration.createParallel(effect));
return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
}
@@ -1903,8 +1903,8 @@ public class VibrationThreadTest {
}
private HalVibration createVibration(CombinedVibration effect) {
- return new HalVibration(mVibrationToken, effect,
- new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
+ effect);
}
private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index cd057b619000..1d1b4e271e19 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -115,6 +115,6 @@ public class VibratorFrameworkStatsLoggerTest {
}
private static VibrationStats.StatsInfo newEmptyStatsInfo() {
- return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
+ return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats());
}
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 538c3fc2ddae..b7821623855c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1791,28 +1791,6 @@ public class VibratorManagerServiceTest {
}
@Test
- public void performHapticFeedback_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration =
- performHapticFeedbackAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
- public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
- VibratorManagerService service = createSystemReadyService();
-
- HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
- service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
- InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
-
- assertTrue(vibration.callerToken == service);
- }
-
- @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
@@ -2147,6 +2125,27 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void cancelVibrate_externalVibration_cancelWithDifferentToken() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ IBinder vibrationBinderToken = mock(IBinder.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class), vibrationBinderToken);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+
+ IBinder cancelBinderToken = mock(IBinder.class);
+ mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken);
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
+ @Test
public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 9b92ff45952b..3ea3235df0f4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -23,6 +23,7 @@ import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS;
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP;
import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
@@ -32,6 +33,7 @@ import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
+import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
import android.provider.Settings;
import android.view.Display;
@@ -236,6 +238,19 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
}
@Test
+ public void stemDoubleKey_behaviorIsLaunchFitness_gestureEventFired() {
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_LAUNCH_DEFAULT_FITNESS_APP);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertKeyGestureEventSentToKeyGestureController(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS);
+ }
+
+ @Test
public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
throws RemoteException {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 1aa908792c0e..a85f8666d2e1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -919,4 +919,9 @@ class TestPhoneWindowManager {
mTestLooper.dispatchAll();
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+
+ void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
+ verify(mInputManagerInternal)
+ .handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 65736cbc519f..c8a35598479f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -179,17 +179,25 @@ class AppCompatActivityRobot {
.getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
}
- void enableTreatmentForTopActivity(boolean enabled) {
- doReturn(enabled).when(mDisplayContent.mAppCompatCameraPolicy)
- .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ void enableFullscreenCameraCompatTreatmentForTopActivity(boolean enabled) {
+ if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ doReturn(enabled).when(
+ mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivityStack.top()));
+ }
}
- void setTopActivityCameraActive(boolean enabled) {
+ void setIsCameraRunningAndWindowingModeEligibleFullscreen(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
.isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()),
/* mustBeFullscreen= */ eq(true));
}
+ void setIsCameraRunningAndWindowingModeEligibleFreeform(boolean enabled) {
+ doReturn(enabled).when(getTopCameraCompatFreeformPolicy())
+ .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()));
+ }
+
void setTopActivityEligibleForOrientationOverride(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
.isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
@@ -508,8 +516,13 @@ class AppCompatActivityRobot {
}
private DisplayRotationCompatPolicy getTopDisplayRotationCompatPolicy() {
- return mActivityStack.top().mDisplayContent
- .mAppCompatCameraPolicy.mDisplayRotationCompatPolicy;
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mDisplayRotationCompatPolicy;
+ }
+
+ private CameraCompatFreeformPolicy getTopCameraCompatFreeformPolicy() {
+ return mActivityStack.top().mDisplayContent.mAppCompatCameraPolicy
+ .mCameraCompatFreeformPolicy;
}
// We add the activity to the stack and spyOn() on its properties.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 1e40aa0c8da8..b83911337c5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -282,7 +282,8 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
robot.activity().createActivityWithComponentInNewTaskAndDisplay();
robot.checkFixedOrientationLetterboxAspectRatioForTopParent(/* expected */ 1.5f);
- robot.activity().enableTreatmentForTopActivity(/* enabled */ true);
+ robot.activity().enableFullscreenCameraCompatTreatmentForTopActivity(
+ /* enabled */ true);
robot.checkAspectRatioForTopParentIsSplitScreenRatio(/* expected */ true);
});
}
@@ -308,6 +309,12 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
super.onPostDisplayContentCreation(displayContent);
spyOn(displayContent.mAppCompatCameraPolicy);
+ if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+ }
+ if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 41102d6922da..9b9040b439c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -20,6 +20,8 @@ import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.AppCompatCameraPolicy.isTreatmentEnabledForActivity;
+import static com.android.server.wm.AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
@@ -194,9 +196,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
public void testIsCameraCompatTreatmentActive_whenTreatmentForTopActivityIsEnabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
- a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ true);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ true);
@@ -206,9 +209,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
public void testIsCameraCompatTreatmentNotActive_whenTreatmentForTopActivityIsDisabled() {
runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
robot.applyOnActivity((a)-> {
a.createActivityWithComponent();
- a.enableTreatmentForTopActivity(/* enabled */ false);
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(/* enabled */ false);
});
robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ false);
@@ -220,9 +224,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsNotRunning() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* enabled */ false);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -234,9 +239,10 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideDisabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
+ robot.allowEnterDesktopMode(true);
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ false);
@@ -245,12 +251,28 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
@Test
@EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
- public void testShouldOverrideMinAspectRatioForCamera_whenCameraIsRunning_overrideEnabled() {
+ public void testShouldOverrideMinAspectRatioForCameraFullscr_cameraIsRunning_overrideEnabled() {
runTestScenario((robot) -> {
robot.applyOnActivity((a)-> {
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ true);
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(/* active */ true);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(/* active */ true);
+ });
+
+ robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
+ });
+ }
+
+
+ @Test
+ @EnableCompatChanges(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldOverrideMinAspectRatioForCameraFreeform_cameraRunning_overrideEnabled() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a)-> {
+ robot.allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(/* active */ true);
});
robot.checkShouldOverrideMinAspectRatioForCamera(/* active */ true);
@@ -318,13 +340,11 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
- assertEquals(getTopAppCompatCameraPolicy()
- .isTreatmentEnabledForActivity(activity().top()), active);
+ assertEquals(active, isTreatmentEnabledForActivity(activity().top()));
}
void checkShouldOverrideMinAspectRatioForCamera(boolean expected) {
- assertEquals(getTopAppCompatCameraPolicy()
- .shouldOverrideMinAspectRatioForCamera(activity().top()), expected);
+ assertEquals(expected, shouldOverrideMinAspectRatioForCamera(activity().top()));
}
// TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
@@ -332,9 +352,5 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
doReturn(isAllowed).when(() ->
DesktopModeHelper.canEnterDesktopMode(any()));
}
-
- private AppCompatCameraPolicy getTopAppCompatCameraPolicy() {
- return activity().top().mDisplayContent.mAppCompatCameraPolicy;
- }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 9057b6cb99ea..76101d51f931 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -37,14 +37,18 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERR
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -321,7 +325,22 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
});
robot.applyOnActivity((a) -> {
a.createActivityWithComponentInNewTaskAndDisplay();
- a.setTopActivityCameraActive(false);
+ a.setIsCameraRunningAndWindowingModeEligibleFullscreen(false);
+ });
+
+ robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
+ /* expected */ SCREEN_ORIENTATION_PORTRAIT);
+ });
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testOverrideOrientationIfNeeded_fullscrOverrideFreeform_cameraActivity_unchanged() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ robot.allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIsCameraRunningAndWindowingModeEligibleFreeform(false);
});
robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_PORTRAIT,
@@ -426,8 +445,8 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
c.enablePolicyForIgnoringRequestedOrientation(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
- a.enableTreatmentForTopActivity(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.enableFullscreenCameraCompatTreatmentForTopActivity(true);
});
robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
@@ -591,5 +610,11 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
return activity().top().mAppCompatController.getOrientationPolicy();
}
+
+ // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
+ void allowEnterDesktopMode(boolean isAllowed) {
+ doReturn(isAllowed).when(() ->
+ DesktopModeHelper.canEnterDesktopMode(any()));
+ }
}
}
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 adc969c40e35..72f4fa9158fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4881,6 +4881,25 @@ public class SizeCompatTests extends WindowTestsBase {
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
}
+
+ @Test
+ @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT})
+ public void testUniversalResizeableByDefault() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ setUpApp(mDisplayContent);
+ assertFalse(mActivity.isUniversalResizeable());
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ final int swDp = mDisplayContent.getConfiguration().smallestScreenWidthDp;
+ if (swDp < WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
+ final int height = 100 + (int) (mDisplayContent.getDisplayMetrics().density
+ * WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
+ resizeDisplay(mDisplayContent, 100 + height, height);
+ }
+ assertTrue(mActivity.isUniversalResizeable());
+ }
+
@Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 401964c2f597..1fa657822189 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -23,6 +23,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -36,6 +40,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -80,6 +85,8 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.InsetsFrameProvider;
@@ -103,6 +110,7 @@ import org.mockito.Mockito;
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.NoSuchElementException;
@@ -967,7 +975,7 @@ public class WindowContainerTests extends WindowTestsBase {
Rect insetsRect = new Rect(0, 200, 1080, 700);
final int flags = FLAG_FORCE_CONSUMING;
final InsetsFrameProvider provider =
- new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+ new InsetsFrameProvider(owner, 1, captionBar())
.setArbitraryRectangle(insetsRect)
.setFlags(flags);
task.addLocalInsetsFrameProvider(provider, owner);
@@ -1678,6 +1686,178 @@ public class WindowContainerTests extends WindowTestsBase {
assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
}
+ @Test
+ public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(0, child1.mMergedExcludeInsetsTypes);
+ assertEquals(0, child11.mMergedExcludeInsetsTypes);
+ assertEquals(0, child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ child11.setExcludeInsetsTypes(navigationBars());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // overwriting the same value has no change
+ for (int i = 0; i < 2; i++) {
+ root.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(),
+ child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+ }
+
+ // set and reset type statusBars on child. Should have no effect because of parent
+ child2.setExcludeInsetsTypes(statusBars());
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // reset
+ child2.setExcludeInsetsTypes(0);
+ assertEquals(statusBars(), root.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child2.mMergedExcludeInsetsTypes);
+ assertEquals(statusBars(), child21.mMergedExcludeInsetsTypes);
+
+ // when parent has statusBars also removed, it should be cleared from all children in the
+ // hierarchy
+ root.setExcludeInsetsTypes(0);
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+
+ // change on node should have no effect on siblings
+ child12.setExcludeInsetsTypes(captionBar());
+ assertEquals(0, root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ assertEquals(ime() | navigationBars(), child11.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar() | ime(), child12.mMergedExcludeInsetsTypes);
+ assertEquals(0, child2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child21.mMergedExcludeInsetsTypes);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_appliedAfterReparenting() {
+ final SurfaceControl mockSurfaceControl = mock(SurfaceControl.class);
+ final DisplayContent mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final WindowContainer root1 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer root2 = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ final WindowContainer child = createWindowContainerSpy(mockSurfaceControl,
+ mockDisplayContent);
+ doNothing().when(child).onConfigurationChanged(any());
+
+ root1.setExcludeInsetsTypes(ime());
+ root2.setExcludeInsetsTypes(captionBar());
+ assertEquals(ime(), root1.mMergedExcludeInsetsTypes);
+ assertEquals(captionBar(), root2.mMergedExcludeInsetsTypes);
+ assertEquals(0, child.mMergedExcludeInsetsTypes);
+ clearInvocations(mockInsetsStateController);
+
+ root1.addChild(child, 0);
+ assertEquals(ime(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // Make sure that reparenting does not call notifyInsetsChanged twice
+ child.reparent(root2, 0);
+ assertEquals(captionBar(), child.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child.asWindowState())));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSetExcludeInsetsTypes_notifyInsetsAfterChange() {
+ final var mockDisplayContent = mock(DisplayContent.class);
+ final var mockInsetsStateController = mock(InsetsStateController.class);
+ doReturn(mockInsetsStateController).when(mockDisplayContent).getInsetsStateController();
+
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final WindowState mockRootWs = mock(WindowState.class);
+ final TestWindowContainer root = builder.setLayer(0).setAsWindowState(mockRootWs).build();
+ root.mDisplayContent = mockDisplayContent;
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(root.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // adding a child (while parent has set excludedInsetsTypes) should trigger
+ // notifyInsetsChanged
+ final WindowState mockChildWs = mock(WindowState.class);
+ final TestWindowContainer child1 = builder.setLayer(0).setAsWindowState(
+ mockChildWs).build();
+ child1.mDisplayContent = mockDisplayContent;
+ root.addChildWindow(child1);
+ // TestWindowContainer overrides onParentChanged and therefore doesn't call into
+ // mergeExcludeInsetsTypesAndNotifyInsetsChanged. This is checked in another test
+ assertTrue(child1.mOnParentChangedCalled);
+ child1.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController).notifyInsetsChanged(
+ new ArraySet<>(List.of(child1.asWindowState())));
+ clearInvocations(mockInsetsStateController);
+
+ // not changing excludedInsetsTypes should not trigger notifyInsetsChanged again
+ root.setExcludeInsetsTypes(ime());
+ assertEquals(ime(), root.mMergedExcludeInsetsTypes);
+ assertEquals(ime(), child1.mMergedExcludeInsetsTypes);
+ verify(mockInsetsStateController, never()).notifyInsetsChanged(any());
+ }
+
+ private WindowContainer<?> createWindowContainerSpy(SurfaceControl mockSurfaceControl,
+ DisplayContent mockDisplayContent) {
+ final WindowContainer<?> wc = spy(new WindowContainer<>(mWm));
+ final WindowState mocWs = mock(WindowState.class);
+ doReturn(mocWs).when(wc).asWindowState();
+ wc.mSurfaceControl = mockSurfaceControl;
+ wc.mDisplayContent = mockDisplayContent;
+ return wc;
+ }
+
private static boolean hasLocalSource(WindowContainer container, int sourceId) {
if (container.mLocalInsetsSources == null) {
return false;
@@ -1693,6 +1873,7 @@ public class WindowContainerTests extends WindowTestsBase {
private boolean mFillsParent;
private boolean mWaitForTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
private boolean mOnParentChangedCalled;
private boolean mOnDescendantOverrideCalled;
@@ -1714,7 +1895,7 @@ public class WindowContainerTests extends WindowTestsBase {
};
TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
- boolean isVisible, boolean waitTransitStart, Integer orientation) {
+ boolean isVisible, boolean waitTransitStart, Integer orientation, WindowState ws) {
super(wm);
mLayer = layer;
@@ -1723,6 +1904,7 @@ public class WindowContainerTests extends WindowTestsBase {
mFillsParent = true;
mOrientation = orientation;
mWaitForTransitStart = waitTransitStart;
+ mWindowState = ws;
spyOn(mSurfaceAnimator);
doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType();
@@ -1790,6 +1972,11 @@ public class WindowContainerTests extends WindowTestsBase {
boolean isWaitingForTransitionStart() {
return mWaitForTransitStart;
}
+
+ @Override
+ WindowState asWindowState() {
+ return mWindowState;
+ }
}
private static class TestWindowContainerBuilder {
@@ -1799,6 +1986,7 @@ public class WindowContainerTests extends WindowTestsBase {
private boolean mIsVisible;
private boolean mIsWaitTransitStart;
private Integer mOrientation;
+ private WindowState mWindowState;
TestWindowContainerBuilder(WindowManagerService wm) {
mWm = wm;
@@ -1806,6 +1994,7 @@ public class WindowContainerTests extends WindowTestsBase {
mIsAnimating = false;
mIsVisible = false;
mOrientation = null;
+ mWindowState = null;
}
TestWindowContainerBuilder setLayer(int layer) {
@@ -1828,6 +2017,11 @@ public class WindowContainerTests extends WindowTestsBase {
return this;
}
+ TestWindowContainerBuilder setAsWindowState(WindowState ws) {
+ mWindowState = ws;
+ return this;
+ }
+
TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
mIsWaitTransitStart = waitTransitStart;
return this;
@@ -1835,7 +2029,7 @@ public class WindowContainerTests extends WindowTestsBase {
TestWindowContainer build() {
return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
- mIsWaitTransitStart, mOrientation);
+ mIsWaitTransitStart, mOrientation, mWindowState);
}
}
diff --git a/services/usb/java/com/UsbDataSignalDisableRequesters.java b/services/usb/java/com/UsbDataSignalDisableRequesters.java
new file mode 100644
index 000000000000..d4d6492ab984
--- /dev/null
+++ b/services/usb/java/com/UsbDataSignalDisableRequesters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.util.ArraySet;
+
+/**
+ * A helper class to store and manage the request for disabling USB port data signaling.
+ *
+ * External requesters are identified by UIDs.
+ * Internal requesters are identified by a reason code enumerated in UsbManagerInternal.
+ *
+ * @hide
+ */
+public final class UsbDataSignalDisableRequesters {
+ final ArraySet<Integer> mExternalUids = new ArraySet<>();
+ final ArraySet<Integer> mInternalReasons = new ArraySet<>();
+
+ public boolean isEmpty() {
+ return mExternalUids.isEmpty() && mInternalReasons.isEmpty();
+ }
+} \ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
new file mode 100644
index 000000000000..c97df6b4f63a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * UsbManagerInternal provides internal APIs for the UsbService to
+ * reduce IPC overhead costs and support internal USB data signal stakers.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class UsbManagerInternal {
+
+ public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {OS_USB_DISABLE_REASON_AAPM})
+ public @interface OsUsbDisableReason {
+ }
+
+ public abstract boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
+
+ public abstract UsbPort[] getPorts();
+
+} \ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9470c0a944c2..ba9dff656f0a 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -46,6 +46,7 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
@@ -69,6 +70,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
@@ -165,8 +167,10 @@ public class UsbService extends IUsbManager.Stub {
private final Object mLock = new Object();
// Key: USB port id
- // Value: A set of UIDs of requesters who request disabling usb data
- private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+ // Value: UsbDataSignalDisableRequesters: UIDs of requesters who request
+ // disabling usb data and disable request reasons by local service callers
+ private final ArrayMap<String, UsbDataSignalDisableRequesters>
+ mUsbDisableRequesters = new ArrayMap<>();
/**
* @return the {@link UsbUserSettingsManager} for the given userId
@@ -221,6 +225,9 @@ public class UsbService extends IUsbManager.Stub {
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
// Ideally we should use the injector pattern so we wouldn't need this constructor for test
@@ -236,6 +243,10 @@ public class UsbService extends IUsbManager.Stub {
mUserManager = userManager;
mSettingsManager = usbSettingsManager;
mPermissionManager = new UsbPermissionManager(context, this);
+
+ if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+ LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ }
}
/**
@@ -903,15 +914,21 @@ public class UsbService extends IUsbManager.Stub {
@Override
public boolean enableUsbData(String portId, boolean enable, int operationId,
IUsbOperationInternal callback) {
- return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
- * Internal function abstracted for testing with callerUid
+ * Manages the enablement of USB data. Requester field could mean two things:
+ * 1. UID of the app that requested USB data to be disabled if caller is external.
+ * 2. Enumberated disable request reason if the caller is internal.
+ *
+ * For internal requests, isInternalRequest should be set to true. Since
+ * internal requests all share the same UID, the request managed separately.
*/
@VisibleForTesting
boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int requester, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -919,7 +936,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, enable, requester, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -949,25 +966,42 @@ public class UsbService extends IUsbManager.Stub {
}
/**
+ * Function to determine if USB data signaling state should be updated.
+ * Depending on if request is internal, input requester should be UID or enumerated disable
+ * reason.
+ *
* If enable = true, exclude UID from update list.
* If enable = false, include UID in update list.
* Return false if enable = true and the list is empty (no updates).
* Return true otherwise (let downstream decide on updates).
*/
- private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+ private boolean shouldUpdateUsbSignaling(String portId, boolean enable,
+ int requester, boolean isInternalRequest) {
+ if(isInternalRequest &&
+ !android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal())
+ return false;
synchronized (mUsbDisableRequesters) {
if (!mUsbDisableRequesters.containsKey(portId)) {
- mUsbDisableRequesters.put(portId, new ArraySet<>());
+ mUsbDisableRequesters.put(portId, new UsbDataSignalDisableRequesters());
}
-
- ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+ UsbDataSignalDisableRequesters disableRequests =
+ mUsbDisableRequesters.get(portId);
if (enable) {
- uidsOfDisableRequesters.remove(uid);
- // re-enable USB port (return true) if there are no other disable requesters
- return uidsOfDisableRequesters.isEmpty();
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.remove(requester);
+ } else {
+ disableRequests.mExternalUids.remove(requester);
+ }
+ // re-enable USB port (return true) if there are no other
+ // disable requesters
+ return disableRequests.isEmpty();
} else {
- uidsOfDisableRequesters.add(uid);
+ if(isInternalRequest) {
+ disableRequests.mInternalReasons.add(requester);
+ } else {
+ disableRequests.mExternalUids.add(requester);
+ }
}
}
return true;
@@ -976,7 +1010,8 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
IUsbOperationInternal callback) {
- enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+ enableUsbDataWhileDockedInternal(portId, operationId, callback,
+ Binder.getCallingUid(), false);
}
/**
@@ -984,7 +1019,7 @@ public class UsbService extends IUsbManager.Stub {
*/
@VisibleForTesting
void enableUsbDataWhileDockedInternal(String portId, int operationId,
- IUsbOperationInternal callback, int callerUid) {
+ IUsbOperationInternal callback, int callerUid, boolean isInternalRequest) {
Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback,
@@ -993,7 +1028,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+ if (!shouldUpdateUsbSignaling(portId, true, callerUid, isInternalRequest)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -1455,10 +1490,11 @@ public class UsbService extends IUsbManager.Stub {
public void onUidRemoved(int uid) {
synchronized (mUsbDisableRequesters) {
for (String portId : mUsbDisableRequesters.keySet()) {
- ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
- if (disabledUid != null) {
- disabledUid.remove(uid);
- if (disabledUid.isEmpty()) {
+ UsbDataSignalDisableRequesters disableRequesters =
+ mUsbDisableRequesters.get(portId);
+ if (disableRequesters != null) {
+ disableRequesters.mExternalUids.remove(uid);
+ if (disableRequesters.isEmpty()) {
enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
new IUsbOperationInternal.Default());
}
@@ -1496,4 +1532,19 @@ public class UsbService extends IUsbManager.Stub {
}
}
}
+
+ private class UsbManagerInternalImpl extends UsbManagerInternal {
+ @Override
+ public boolean enableUsbData(String portId, boolean enable,
+ int operationId, IUsbOperationInternal callback,
+ @OsUsbDisableReason int disableReason) {
+ return enableUsbDataInternal(portId, enable, operationId, callback,
+ disableReason, true);
+ }
+
+ @Override
+ public UsbPort[] getPorts() {
+ return mPortManager.getPorts();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff302f6b1a65..ff966ae66e0a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2298,13 +2298,9 @@ public class TelephonyManager {
*
* See {@link #getImei(int)} for details on the required permissions and behavior
* when the caller does not hold sufficient permissions.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei() {
return getImei(getSlotIndex());
}
@@ -2343,13 +2339,9 @@ public class TelephonyManager {
* </ul>
*
* @param slotIndex of which IMEI is returned
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -2366,11 +2358,7 @@ public class TelephonyManager {
/**
* Returns the Type Allocation Code from the IMEI. Return null if Type Allocation Code is not
* available.
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
public String getTypeAllocationCode() {
return getTypeAllocationCode(getSlotIndex());
@@ -2381,11 +2369,7 @@ public class TelephonyManager {
* available.
*
* @param slotIndex of which Type Allocation Code is returned
- *
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
public String getTypeAllocationCode(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -19378,12 +19362,9 @@ public class TelephonyManager {
* </ul>
*
* @return Primary IMEI of type string
- * @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
* @throws SecurityException if the caller does not have the required permission/privileges
*/
@NonNull
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
public String getPrimaryImei() {
try {
ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index bd5c7597ba14..49ca6f34d2d9 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -249,6 +249,13 @@ public final class SatelliteManager {
public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite";
/**
+ * Bundle key to get the response from
+ * {@link #deprovisionSatellite(List, Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_DEPROVISION_SATELLITE_TOKENS = "deprovision_satellite";
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -2791,6 +2798,61 @@ public final class SatelliteManager {
}
}
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_DEPROVISION_SATELLITE_TOKENS)) {
+ boolean isUpdated =
+ resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(isUpdated)));
+ } else {
+ loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.deprovisionSatellite(list, receiver);
+ } else {
+ loge("deprovisionSatellite() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("deprovisionSatellite() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3161d17681ed..61f01461232f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3444,4 +3444,15 @@ interface ITelephony {
*/
boolean overrideCarrierRoamingNtnEligibilityChanged(
in boolean status, in boolean resetRequired);
+
+ /**
+ * Deliver the list of deprovisioned satellite subscriber infos.
+ *
+ * @param list The list of deprovisioned satellite subscriber infos.
+ * @param result The result receiver that returns whether deliver success or fail.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void deprovisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result);
}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 9d56a92fad52..8ecddaa76216 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import static org.junit.Assert.assertThrows;
+
import android.platform.test.annotations.Presubmit;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -44,8 +46,29 @@ public class ProtoLogTest {
.containsExactly(TEST_GROUP_1, TEST_GROUP_2);
}
+ @Test
+ public void throwOnRegisteringDuplicateGroup() {
+ final var assertion = assertThrows(RuntimeException.class,
+ () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2));
+
+ Truth.assertThat(assertion).hasMessageThat().contains("" + TEST_GROUP_1.getId());
+ Truth.assertThat(assertion).hasMessageThat().contains("duplicate");
+ }
+
+ @Test
+ public void throwOnRegisteringGroupsWithIdCollisions() {
+ final var assertion = assertThrows(RuntimeException.class,
+ () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_WITH_COLLISION, TEST_GROUP_2));
+
+ Truth.assertThat(assertion).hasMessageThat()
+ .contains("" + TEST_GROUP_WITH_COLLISION.getId());
+ Truth.assertThat(assertion).hasMessageThat().contains("collision");
+ }
+
private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+ private static final IProtoLogGroup TEST_GROUP_WITH_COLLISION =
+ new ProtoLogGroup("TEST_TAG_WITH_COLLISION", 1);
private static class ProtoLogGroup implements IProtoLogGroup {
private final boolean mEnabled;
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 56845aeb6a2c..51d57f0a0de9 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -18,6 +18,10 @@ package com.android.server.usb;
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -31,12 +35,15 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
+import android.hardware.usb.UsbPort;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -71,26 +78,38 @@ public class UsbServiceTest {
private static final int TEST_SECOND_CALLER_ID = 2000;
+ private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100;
+
+ private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200;
+
private UsbService mUsbService;
+ private UsbManagerInternal mUsbManagerInternal;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
+ LocalServices.removeAllServicesForTest();
MockitoAnnotations.initMocks(this);
- when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
- eq(mCallback), any())).thenReturn(true);
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(),
+ eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true);
mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
mUserManager, mUsbSettingsManager);
+ mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
+ assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
+ .that(mUsbManagerInternal).isNotNull();
}
- private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+ private void assertToggleUsbSuccessfully(int requester, boolean enable,
+ boolean isInternalRequest) {
assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
enable, TEST_TRANSACTION_ID, mCallback, null);
@@ -100,9 +119,10 @@ public class UsbServiceTest {
clearInvocations(mCallback);
}
- private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+ private void assertToggleUsbFailed(int requester, boolean enable,
+ boolean isInternalRequest) throws Exception {
assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
@@ -116,15 +136,16 @@ public class UsbServiceTest {
*/
@Test
public void disableUsb_successfullyDisable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
}
/**
- * Verify enableUsbData successfully enables USB port without error given no other stakers
+ * Verify enableUsbData successfully enables USB port without error given
+ * no other stakers
*/
@Test
public void enableUsbWhenNoOtherStakers_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
@@ -132,47 +153,132 @@ public class UsbServiceTest {
*/
@Test
public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+ assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false);
}
/**
- * Verify enableUsbData successfully enables USB port when the last staker is removed
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed
*/
@Test
public void enableUsbByTheOnlyStaker_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
- * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+ * Verify enableUsbDataWhileDockedInternal does not enable USB port if other
+ * stakers are present
*/
@Test
public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
throws RemoteException {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
/**
- * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
- * not present
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other
+ * stakers are not present
*/
@Test
public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, null);
verifyZeroInteractions(mCallback);
}
+
+ /**
+ * Verify enableUsbData successfully enables USB port without error given no
+ * other stakers for internal requests
+ */
+ @Test
+ public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other external stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for external requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false);
+ }
+
+ /**
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed for internal requests
+ */
+ @Test
+ public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false);
+
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
+ }
+
+ /**
+ * Verify USB Manager internal calls mPortManager to get UsbPorts
+ */
+ @Test
+ public void usbManagerInternal_getPorts_callsPortManager() {
+ when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
+
+ UsbPort[] ports = mUsbManagerInternal.getPorts();
+
+ verify(mUsbPortManager).getPorts();
+ assertEquals(ports.length, 0);
+ }
+
+ @Test
+ public void usbManagerInternal_enableUsbData_successfullyEnable() {
+ boolean desiredEnableState = true;
+
+ assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
+ TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+
+ verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+ desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
+ verifyZeroInteractions(mCallback);
+ clearInvocations(mUsbPortManager);
+ clearInvocations(mCallback);
+ }
}
diff --git a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
index 81814b67f5ee..7bc9970629a6 100644
--- a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
@@ -25,6 +25,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiConfiguration;
@@ -39,6 +40,7 @@ public class VcnTransportInfoTest {
private static final int SUB_ID = 1;
private static final int NETWORK_ID = 5;
private static final int MIN_UDP_PORT_4500_NAT_TIMEOUT = 120;
+ private static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_INVALID = 119;
private static final WifiInfo WIFI_INFO =
new WifiInfo.Builder().setNetworkId(NETWORK_ID).build();
@@ -48,6 +50,27 @@ public class VcnTransportInfoTest {
new VcnTransportInfo(WIFI_INFO, MIN_UDP_PORT_4500_NAT_TIMEOUT);
@Test
+ public void testBuilder() {
+ final VcnTransportInfo transportInfo =
+ new VcnTransportInfo.Builder()
+ .setMinUdpPort4500NatTimeoutSeconds(MIN_UDP_PORT_4500_NAT_TIMEOUT)
+ .build();
+
+ assertEquals(
+ MIN_UDP_PORT_4500_NAT_TIMEOUT, transportInfo.getMinUdpPort4500NatTimeoutSeconds());
+ }
+
+ @Test
+ public void testBuilder_withInvalidNatTimeout() {
+ try {
+ new VcnTransportInfo.Builder()
+ .setMinUdpPort4500NatTimeoutSeconds(MIN_UDP_PORT_4500_NAT_TIMEOUT_INVALID);
+ fail("Expected to fail due to invalid NAT timeout");
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
public void testGetWifiInfo() {
assertEquals(WIFI_INFO, WIFI_UNDERLYING_INFO.getWifiInfo());
diff --git a/tests/vcn/java/android/net/vcn/VcnUtilsTest.java b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
new file mode 100644
index 000000000000..3ce6c8f9386d
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUtilsTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.wifi.WifiInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class VcnUtilsTest {
+ private static final int SUB_ID = 1;
+
+ private static final WifiInfo WIFI_INFO = new WifiInfo.Builder().build();
+ private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+ new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+ private static final VcnTransportInfo VCN_TRANSPORT_INFO =
+ new VcnTransportInfo.Builder().build();
+
+ private ConnectivityManager mMockConnectivityManager;
+ private Network mMockWifiNetwork;
+ private Network mMockCellNetwork;
+
+ private NetworkCapabilities mVcnCapsWithUnderlyingWifi;
+ private NetworkCapabilities mVcnCapsWithUnderlyingCell;
+
+ @Before
+ public void setUp() {
+ mMockConnectivityManager = mock(ConnectivityManager.class);
+
+ mMockWifiNetwork = mock(Network.class);
+ mVcnCapsWithUnderlyingWifi = newVcnCaps(VCN_TRANSPORT_INFO, mMockWifiNetwork);
+ final NetworkCapabilities wifiCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(WIFI_INFO)
+ .build();
+ when(mMockConnectivityManager.getNetworkCapabilities(mMockWifiNetwork))
+ .thenReturn(wifiCaps);
+
+ mMockCellNetwork = mock(Network.class);
+ mVcnCapsWithUnderlyingCell = newVcnCaps(VCN_TRANSPORT_INFO, mMockCellNetwork);
+ final NetworkCapabilities cellCaps =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+ .build();
+ when(mMockConnectivityManager.getNetworkCapabilities(mMockCellNetwork))
+ .thenReturn(cellCaps);
+ }
+
+ private static NetworkCapabilities newVcnCaps(
+ VcnTransportInfo vcnTransportInfo, Network underlyingNetwork) {
+ return new NetworkCapabilities.Builder()
+ .setTransportInfo(vcnTransportInfo)
+ .setUnderlyingNetworks(Collections.singletonList(underlyingNetwork))
+ .build();
+ }
+
+ @Test
+ public void getWifiInfoFromVcnCaps() {
+ assertEquals(
+ WIFI_INFO,
+ VcnUtils.getWifiInfoFromVcnCaps(
+ mMockConnectivityManager, mVcnCapsWithUnderlyingWifi));
+ }
+
+ @Test
+ public void getWifiInfoFromVcnCaps_onVcnWithUnderlyingCell() {
+ assertNull(
+ VcnUtils.getWifiInfoFromVcnCaps(
+ mMockConnectivityManager, mVcnCapsWithUnderlyingCell));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps() {
+ assertEquals(
+ SUB_ID,
+ VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, mVcnCapsWithUnderlyingCell));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_onVcnWithUnderlyingWifi() {
+ assertEquals(
+ INVALID_SUBSCRIPTION_ID,
+ VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, mVcnCapsWithUnderlyingWifi));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_onNonVcnNetwork() {
+ assertEquals(
+ INVALID_SUBSCRIPTION_ID,
+ VcnUtils.getSubIdFromVcnCaps(
+ mMockConnectivityManager, new NetworkCapabilities.Builder().build()));
+ }
+
+ @Test
+ public void getSubIdFromVcnCaps_withMultipleUnderlyingNetworks() {
+ final NetworkCapabilities vcnCaps =
+ new NetworkCapabilities.Builder(mVcnCapsWithUnderlyingCell)
+ .setUnderlyingNetworks(
+ Arrays.asList(
+ new Network[] {mMockCellNetwork, mock(Network.class)}))
+ .build();
+ assertEquals(SUB_ID, VcnUtils.getSubIdFromVcnCaps(mMockConnectivityManager, vcnCaps));
+ }
+}
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 1af8d6f67bd3..b2e48bd74e8a 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -40,7 +40,7 @@ void strcpy16_htod(uint16_t* dst, const char16_t* src)
void printStringPool(const ResStringPool* pool)
{
if (pool->getError() == NO_INIT) {
- printf("String pool is unitialized.\n");
+ printf("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printf("String pool is corrupt/invalid.\n");
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 064b4617b0a2..2527dcd26382 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -445,7 +445,7 @@ void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer*
using namespace android;
if (pool->getError() == NO_INIT) {
- printer->Print("String pool is unitialized.\n");
+ printer->Print("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printer->Print("String pool is corrupt/invalid.\n");
diff --git a/tools/processors/property_cache/Android.bp b/tools/processors/property_cache/Android.bp
new file mode 100644
index 000000000000..81fab7a4c862
--- /dev/null
+++ b/tools/processors/property_cache/Android.bp
@@ -0,0 +1,57 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_framework_android_multiuser",
+}
+
+java_library_host {
+ name: "libcached-property-annotation-processor",
+ srcs: [
+ ":framework-annotations",
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "codegen-version-info",
+ "android.multiuser.flags-aconfig-java-host",
+ "guava",
+ ],
+ use_tools_jar: true,
+}
+
+java_plugin {
+ name: "cached-property-annotation-processor",
+ processor_class: "android.processor.property_cache.CachedPropertyProcessor",
+ static_libs: ["libcached-property-annotation-processor"],
+}
+
+java_aconfig_library {
+ name: "android.multiuser.flags-aconfig-java-host",
+ aconfig_declarations: "android.multiuser.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_test_host {
+ name: "cached-property-annotation-processor-test",
+ srcs: ["test/java/**/*.java"],
+ java_resources: [":CachedPropertyAnnotationJavaTestSource"],
+ static_libs: [
+ "compile-testing-prebuilt",
+ "truth",
+ "junit",
+ "guava",
+ "libcached-property-annotation-processor",
+ ],
+ test_suites: ["general-tests"],
+}
+
+filegroup {
+ name: "CachedPropertyAnnotationJavaTestSource",
+ srcs: ["test/resources/*.java"],
+ path: "test/resources/",
+ visibility: ["//visibility:private"],
+}
diff --git a/tools/processors/property_cache/TEST_MAPPING b/tools/processors/property_cache/TEST_MAPPING
new file mode 100644
index 000000000000..7177abc8c52d
--- /dev/null
+++ b/tools/processors/property_cache/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "cached-property-annotation-processor-test"
+ }
+ ]
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
new file mode 100644
index 000000000000..c665c840f376
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.base.CaseFormat;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class CacheConfig {
+ private final CacheModifiers mModifiers;
+ private final int mMaxSize;
+ private final String mModuleName;
+ private final String mApiName;
+ private final String mClassName;
+ private final String mQualifiedName;
+ private String mPropertyName;
+ private String mMethodName;
+ private int mNumberOfParams = 0;
+ private String mInputType = Constants.JAVA_LANG_VOID;
+ private String mResultType;
+
+ public CacheConfig(TypeElement classElement, ExecutableElement method) {
+ CachedPropertyDefaults classAnnotation = classElement.getAnnotation(
+ CachedPropertyDefaults.class);
+ CachedProperty methodAnnotation = method.getAnnotation(CachedProperty.class);
+
+ mModuleName = methodAnnotation.module().isEmpty() ? classAnnotation.module()
+ : methodAnnotation.module();
+ mClassName = classElement.getSimpleName().toString();
+ mQualifiedName = classElement.getQualifiedName().toString();
+ mModifiers = new CacheModifiers(methodAnnotation.modsFlagOnOrNone());
+ mMethodName = method.getSimpleName().toString();
+ mPropertyName = getPropertyName(mMethodName);
+ mApiName = methodAnnotation.api().isEmpty() ? getUniqueApiName(mClassName, mPropertyName)
+ : methodAnnotation.api();
+ mMaxSize = methodAnnotation.max() == -1 ? classAnnotation.max() : methodAnnotation.max();
+ mNumberOfParams = method.getParameters().size();
+ if (mNumberOfParams > 0) {
+ mInputType = primitiveTypeToObjectEquivalent(
+ method.getParameters().get(0).asType().toString());
+ }
+ mResultType = primitiveTypeToObjectEquivalent(method.getReturnType().toString());
+ }
+
+ public CacheModifiers getModifiers() {
+ return mModifiers;
+ }
+
+ public int getMaxSize() {
+ return mMaxSize;
+ }
+
+ public String getApiName() {
+ return mApiName;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ public String getQualifiedName() {
+ return mQualifiedName;
+ }
+
+ public String getModuleName() {
+ return mModuleName;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ public String getPropertyVariable() {
+ return (mModifiers.isStatic() ? "s" : "m") + mPropertyName;
+ }
+
+ private String getPropertyName(String methodName) {
+ if (methodName.startsWith("get")) {
+ return methodName.substring(3);
+ } else if (methodName.startsWith("is")) {
+ return methodName.substring(2);
+ } else {
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, methodName);
+ }
+ }
+
+ public int getNumberOfParams() {
+ return mNumberOfParams;
+ }
+
+ public String getInputType() {
+ return mInputType;
+ }
+
+ public String getResultType() {
+ return mResultType;
+ }
+
+ /**
+ * This method returns the unique api name for a given class and property name.
+ * Property name is retrieved from the method name.
+ * Both names are combined and converted to lower snake case.
+ *
+ * @param className The name of the class that contains the property.
+ * @param propertyName The name of the property.
+ * @return The registration name for the property.
+ */
+ private String getUniqueApiName(String className, String propertyName) {
+ return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, className + propertyName);
+ }
+
+ private String primitiveTypeToObjectEquivalent(String simpleType) {
+ // checking against primitive types
+ return Constants.PRIMITIVE_TYPE_MAP.getOrDefault(simpleType, simpleType);
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
new file mode 100644
index 000000000000..fda9b2c40e27
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CacheModifier;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CacheModifiers {
+ private final boolean mIsStatic;
+ private static final String STATIC_MODIFIER_STRING = "static ";
+
+ CacheModifiers(CacheModifier[] modifierArray) {
+ final List<CacheModifier> modifiers = Arrays.asList(modifierArray);
+ mIsStatic = modifiers.contains(CacheModifier.STATIC);
+ }
+
+ public boolean isStatic() {
+ return mIsStatic;
+ }
+
+ public String getStaticModifier() {
+ return mIsStatic ? STATIC_MODIFIER_STRING : Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
new file mode 100644
index 000000000000..03610128d269
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+public class CachedPropertyProcessor extends AbstractProcessor {
+
+ IpcDataCacheComposer mIpcDataCacheComposer =
+ new IpcDataCacheComposer();
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return new HashSet<String>(
+ ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName()));
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(CachedPropertyDefaults.class)) {
+ try {
+ generateCachedClass((TypeElement) element, processingEnv.getFiler());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private void generateCachedClass(TypeElement classElement, Filer filer) throws IOException {
+ String packageName =
+ processingEnv
+ .getElementUtils()
+ .getPackageOf(classElement)
+ .getQualifiedName()
+ .toString();
+ String className = classElement.getSimpleName().toString() + "Cache";
+ JavaFileObject jfo = filer.createSourceFile(packageName + "." + className);
+ Writer writer = jfo.openWriter();
+ writer.write("package " + packageName + ";\n\n");
+ writer.write("import android.os.IpcDataCache;\n");
+ writer.write("\n /** \n * This class is auto-generated \n * @hide \n **/");
+ writer.write("\npublic class " + className + " {\n");
+
+ List<ExecutableElement> methods =
+ ElementFilter.methodsIn(classElement.getEnclosedElements());
+ String initCache = String.format(Constants.METHOD_COMMENT,
+ " - initialise all caches for class " + className)
+ + "\npublic static void initCache() {";
+ for (ExecutableElement method : methods) {
+ if (method.getAnnotation(CachedProperty.class) != null) {
+ mIpcDataCacheComposer.generatePropertyCache(writer, classElement, method);
+ initCache += "\n " + mIpcDataCacheComposer.generateInvalidatePropertyCall();
+ }
+ }
+ initCache += "\n}";
+ writer.write(initCache);
+ writer.write("\n}");
+ writer.write("\n");
+ writer.close();
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
new file mode 100644
index 000000000000..03961bcaaba0
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import com.google.common.collect.ImmutableMap;
+
+public final class Constants {
+ public static final String EMPTY_STRING = "";
+ public static final String JAVA_LANG_VOID = "java.lang.Void";
+ public static final ImmutableMap<String, String> PRIMITIVE_TYPE_MAP =
+ ImmutableMap.of(
+ "int", "java.lang.Integer",
+ "boolean", "java.lang.Boolean",
+ "long", "java.lang.Long",
+ "float", "java.lang.Float",
+ "double", "java.lang.Double",
+ "byte", "java.lang.Byte",
+ "short", "java.lang.Short",
+ "char", "java.lang.Character");
+
+ public static final String METHOD_COMMENT = "\n /**"
+ + "\n * This method is auto-generated%s"
+ + "\n * "
+ + "\n * @hide"
+ + "\n */";
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
new file mode 100644
index 000000000000..8526a04e9910
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class IpcDataCacheComposer {
+
+ private static final String PROPERTY_DEFINITION_LINE = "private %s%s %s;\n";
+ private static final String METHOD_NAME_LINE = "\npublic %s%s %s(%s%s%s\n) {\n";
+ private static final String RETURN_IF_NOT_NULL_LINE =
+ "if (%s != null) {\n return %s.%s;\n }";
+
+ private CacheConfig mCacheConfig;
+
+ /**
+ * Generates code for property cache.
+ *
+ * @param writer writer to write code to.
+ * @param classElement class element to generate code for.
+ * @param method method element to generate code for.
+ * @throws IOException if writer throws IOException.
+ */
+ public void generatePropertyCache(Writer writer, TypeElement classElement,
+ ExecutableElement method) throws IOException {
+
+ mCacheConfig = new CacheConfig(classElement, method);
+
+ ParamComposer inputParam = new ParamComposer(null, null);
+ ParamComposer binderParam = new ParamComposer(
+ String.format("IpcDataCache.RemoteCall<%s, %s>", mCacheConfig.getInputType(),
+ mCacheConfig.getResultType()), "binderCall");
+
+ ParamComposer bypassParam = new ParamComposer(null, null); // empty if method have no params
+ String queryCall = "query(null)";
+ if (mCacheConfig.getNumberOfParams() > 0) {
+ bypassParam = new ParamComposer(
+ String.format("IpcDataCache.BypassCall<%s> ", mCacheConfig.getInputType()),
+ "bypassPredicate");
+ inputParam = new ParamComposer(mCacheConfig.getInputType(), "query");
+ queryCall = "query(query)";
+ }
+ String propertyClass =
+ "IpcDataCache<" + mCacheConfig.getInputType() + ", " + mCacheConfig.getResultType()
+ + ">";
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ String lockObject = mCacheConfig.getPropertyVariable() + "Lock";
+ writer.write("private " + mCacheConfig.getModifiers().getStaticModifier() + "final Object "
+ + lockObject + " = new Object();\n");
+ writer.write(String.format(PROPERTY_DEFINITION_LINE,
+ mCacheConfig.getModifiers().getStaticModifier(), propertyClass,
+ mCacheConfig.getPropertyVariable()));
+
+ writer.write(propertyInvalidatedCacheMethod(binderParam, bypassParam, inputParam, queryCall,
+ lockObject));
+
+ // If binder param is not empty then generate getter without binder param to be called
+ if (!bypassParam.getParam().isEmpty()) {
+ writer.write(propertyInvalidatedCacheMethod(binderParam, new ParamComposer(null, null),
+ inputParam, queryCall, lockObject));
+ }
+ writer.write(String.format(Constants.METHOD_COMMENT,
+ "- invalidate cache for {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + "}"));
+ writer.write("\n public static final void " + invalidateName + "() {");
+ writer.write(
+ "\n IpcDataCache.invalidateCache(\"" + mCacheConfig.getModuleName() + "\", \""
+ + mCacheConfig.getApiName() + "\");");
+ writer.write("\n }");
+ writer.write("\n");
+ writer.write("\n");
+ }
+
+ /**
+ * Generates code to call cache invalidation.
+ *
+ * @return code string calling cache invalidation.
+ */
+ public String generateInvalidatePropertyCall() {
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ return mCacheConfig.getClassName() + "Cache." + invalidateName + "();";
+ }
+
+ /**
+ * Generates code for getter that returns cached value or calls binder and caches result.
+ *
+ * @param binderParam parameter for binder call.
+ * @param bypassParam parameter for bypass predicate.
+ * @param inputParam parameter for input value.
+ * @param queryCall cache query call syntax.
+ * @param lockObject object to synchronize on.
+ * @return String with code for method.
+ */
+ private String propertyInvalidatedCacheMethod(ParamComposer binderParam,
+ ParamComposer bypassParam, ParamComposer inputParam, String queryCall,
+ String lockObject) {
+ String result = "\n";
+ CacheModifiers modifiers = mCacheConfig.getModifiers();
+ String paramsComments = binderParam.getParamComment(
+ "lambda for remote call" + " {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + " }") + bypassParam.getParamComment(
+ "lambda to bypass remote call") + inputParam.getParamComment(
+ "parameter to call remote lambda");
+ result += String.format(Constants.METHOD_COMMENT, paramsComments);
+ result += String.format(METHOD_NAME_LINE, modifiers.getStaticModifier(),
+ mCacheConfig.getResultType(), mCacheConfig.getMethodName(),
+ binderParam.getParam(), bypassParam.getNextParam(),
+ inputParam.getNextParam());
+ result += String.format(RETURN_IF_NOT_NULL_LINE, mCacheConfig.getPropertyVariable(),
+ mCacheConfig.getPropertyVariable(), queryCall);
+ result += "\n synchronized (" + lockObject + " ) {";
+ result += "\n if (" + mCacheConfig.getPropertyVariable() + " == null) {";
+ result += "\n " + mCacheConfig.getPropertyVariable() + " = new IpcDataCache" + "("
+ + generateCreateIpcConfig() + ", " + binderParam.getName()
+ + bypassParam.getNextName() + ");\n";
+ result += "\n }";
+ result += "\n }";
+ result += "\n return " + mCacheConfig.getPropertyVariable() + "." + queryCall + ";";
+ result += "\n }";
+ result += "\n";
+ return result;
+ }
+
+ /**
+ * Generates code for new IpcDataCache.Config object for given configuration.
+ *
+ * @return String with code for new IpcDataCache.Config object.
+ */
+ public String generateCreateIpcConfig() {
+ return "new IpcDataCache.Config(" + mCacheConfig.getMaxSize() + ", " + "\""
+ + mCacheConfig.getModuleName() + "\"" + ", " + "\"" + mCacheConfig.getApiName()
+ + "\"" + ", " + "\"" + mCacheConfig.getPropertyName() + "\"" + ")";
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
new file mode 100644
index 000000000000..307443aea730
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache;
+
+public class ParamComposer {
+ private String mType;
+ private String mName;
+
+ /** Creates ParamComposer with given type and name.
+ *
+ * @param type type of parameter.
+ * @param name name of parameter.
+ */
+ public ParamComposer(String type, String name) {
+ mType = type;
+ mName = name;
+ }
+
+ /** Returns name of parameter.
+ *
+ * @return name of parameter.
+ */
+ public String getName() {
+ if (mName != null) {
+ return mName;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /** Returns name of parameter for next parameter followed by comma.
+ *
+ * @return name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextName() {
+ if (!getName().isEmpty()) {
+ return ", " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type of parameter.
+ *
+ * @return type of parameter.
+ */
+ public String getType() {
+ if (mType != null) {
+ return mType;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter.
+ *
+ * @return type and name of parameter if exists, empty string otherwise.
+ */
+ public String getParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return getType() + " " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter for next parameter followed by comma.
+ *
+ * @return type and name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return ", " + getParam();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns comment for parameter.
+ *
+ * @param description of parameter.
+ * @return comment for parameter if exists, empty string otherwise.
+ */
+ public String getParamComment(String description) {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return "\n * @param " + getName() + " - " + description;
+ }
+ return Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
new file mode 100644
index 000000000000..1e23c78d4816
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import android.processor.property_cache.CachedPropertyProcessor;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Tests the {@link CachedPropertyProcessor}. */
+@RunWith(JUnit4.class)
+public class CachedPropertyProcessorTest {
+ private final Compiler mCompiler =
+ Compiler.javac().withProcessors(new CachedPropertyProcessor());
+
+ @Test
+ public void testDefaultValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("DefaultCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Default.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/DefaultCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+
+ @Test
+ public void testCustomValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("CustomCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Custom.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/CustomCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
new file mode 100644
index 000000000000..e5ef48c14436
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+// Mocked class for generation compilation tests purposes only.
+public class IpcDataCache<Input, Output> {
+ public static class Config {
+ public Config(int max, String module, String api, String name) {
+ }
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks.
+ * @return null
+ */
+ public Output query(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param key - shadow parameter from IpcDataCache in Frameworks;
+ */
+ public static void invalidateCache(String key) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return null
+ */
+ public Output recompute(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache in android framework.
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return false
+ */
+ public boolean bypass(Input query) {
+ return false;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - parameter equivalent to IpcDataCache in android framework.
+ * @param key - parameter equivalent to IpcDataCache in android framework.
+ * @return module + key sttring
+ */
+ public static String createPropertyName(String module, String key) {
+ return module + key;
+ }
+
+ public abstract static class QueryHandler<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ * @return expected value
+ */
+ public abstract Output apply(Input query);
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ */
+ public boolean shouldBypassCache(Input query) {
+ return false;
+ }
+ }
+
+ public interface RemoteCall<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.RemoteCall in android framework.
+ */
+ Output apply(Input query);
+ }
+
+ public interface BypassCall<Input> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.BypassCall in android framework.
+ */
+ boolean apply(Input query);
+ }
+
+ public IpcDataCache(
+ int maxEntries,
+ String module,
+ String api,
+ String cacheName,
+ QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer,
+ BypassCall<Input> bypassCall) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.*/
+ public void invalidateCache() {
+ }
+
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - shadow parameter from IpcDataCache in Frameworks.
+ * @param api - shadow parameter from IpcDataCache in Frameworks.
+ */
+ public static void invalidateCache(String module, String api) {
+ }
+
+}
diff --git a/tools/processors/property_cache/test/resources/Custom.java b/tools/processors/property_cache/test/resources/Custom.java
new file mode 100644
index 000000000000..05024da67e6c
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Custom.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults(max = 4, module = "bluetooth")
+public class Custom {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new CustomCache();
+
+ public Custom() {
+ CustomCache.initCache();
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return CustomCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return CustomCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((CustomCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC}, max = 1)
+ public int getDaysTillMyBirthday() {
+ return CustomCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1 and custom
+ * api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {}, max = 1, api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((CustomCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return CustomCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+ int mDaysTillBirthday = 182;
+
+ public Date getBirthday(int userId) {
+ return new Date(2024, 6, 1 + userId);
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return mDaysTillBirthday + userId;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 365 - getDaysTillBirthday(userId);
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 365;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/CustomCache.java b/tools/processors/property_cache/test/resources/CustomCache.java
new file mode 100644
index 000000000000..326467fb5639
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/CustomCache.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class CustomCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall);
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "custom_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday
+ * }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "custom_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class CustomCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ CustomCache.invalidateBirthday();
+ CustomCache.invalidateDaysTillBirthday();
+ CustomCache.invalidateDaysSinceBirthday();
+ CustomCache.invalidateDaysTillMyBirthday();
+ CustomCache.invalidateDaysSinceMyBirthday();
+ CustomCache.invalidateBirthdayWishesFromUser();
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/Default.java b/tools/processors/property_cache/test/resources/Default.java
new file mode 100644
index 000000000000..d2449aad656c
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Default.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults()
+public class Default {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new DefaultCache();
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return DefaultCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return DefaultCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /** Testing generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((DefaultCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {CacheModifier.STATIC},
+ max = 1)
+ public int getDaysTillMyBirthday() {
+ return DefaultCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1 and
+ custom api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {},
+ max = 1,
+ api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((DefaultCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return DefaultCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+
+ BirthdayManagerService() {
+ DefaultCache.initCache();
+ }
+
+ public Date getBirthday(int userId) {
+ return new Date();
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 0;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/DefaultCache.java b/tools/processors/property_cache/test/resources/DefaultCache.java
new file mode 100644
index 000000000000..9531118752bb
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/DefaultCache.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.processor.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class DefaultCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "default_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceMyBirthday }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ *
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "default_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class DefaultCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ DefaultCache.invalidateBirthday();
+ DefaultCache.invalidateDaysTillBirthday();
+ DefaultCache.invalidateDaysSinceBirthday();
+ DefaultCache.invalidateDaysTillMyBirthday();
+ DefaultCache.invalidateDaysSinceMyBirthday();
+ DefaultCache.invalidateBirthdayWishesFromUser();
+ }
+}