summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java88
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java30
-rw-r--r--core/api/current.txt269
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/java/android/accessibilityservice/BrailleDisplayController.java2
-rw-r--r--core/java/android/app/OWNERS1
-rw-r--r--core/java/android/app/RESOURCES_OWNERS6
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig11
-rw-r--r--core/java/android/app/contextualsearch/CallbackToken.java15
-rw-r--r--core/java/android/app/contextualsearch/ContextualSearchManager.java2
-rw-r--r--core/java/android/app/slice/Slice.java10
-rw-r--r--core/java/android/app/slice/SliceItem.java5
-rw-r--r--core/java/android/app/slice/SliceManager.java5
-rw-r--r--core/java/android/app/slice/SliceMetrics.java5
-rw-r--r--core/java/android/app/slice/SliceProvider.java5
-rw-r--r--core/java/android/app/slice/SliceSpec.java5
-rw-r--r--core/java/android/hardware/devicestate/DeviceState.java14
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateInfo.java11
-rw-r--r--core/java/android/view/ViewRootImpl.java8
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig8
-rw-r--r--core/java/android/widget/AbsListView.java11
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java67
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/PaintContext.java71
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Platform.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java418
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java97
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java102
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java121
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java113
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java89
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java105
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java102
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java78
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java102
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java119
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java88
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java121
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java114
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Header.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java73
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java82
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java74
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java88
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java79
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java79
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java83
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java176
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java39
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java41
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java829
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java130
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java81
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java64
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java512
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java115
-rw-r--r--core/tests/coretests/AndroidManifest.xml11
-rw-r--r--core/tests/coretests/res/layout/activity_abslist_view.xml28
-rw-r--r--core/tests/coretests/src/android/content/res/ResourcesManagerTest.java41
-rw-r--r--core/tests/coretests/src/android/widget/AbsListViewActivity.java34
-rw-r--r--core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java126
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java42
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java3
-rw-r--r--graphics/java/android/graphics/fonts/FontFamily.java26
-rw-r--r--libs/WindowManager/Shell/Android.bp12
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java)6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java34
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java31
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java31
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java31
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java2
-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/Bubbles.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java2
-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/DesktopTasksController.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java1
-rw-r--r--media/java/android/media/MediaCodec.java20
-rw-r--r--media/java/android/media/MediaCodecInfo.java9
-rw-r--r--media/jni/Android.bp3
-rw-r--r--media/jni/audioeffect/Android.bp3
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java37
-rw-r--r--packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml26
-rw-r--r--packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml74
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml24
-rw-r--r--packages/SettingsLib/res/values/strings.xml7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java67
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java10
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt10
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt23
-rw-r--r--packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java5
-rw-r--r--packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml25
-rw-r--r--packages/SystemUI/res/layout/fullscreen_magnification_border.xml22
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java140
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt317
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java19
-rw-r--r--services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java53
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java12
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java53
-rw-r--r--services/core/java/com/android/server/Watchdog.java1
-rw-r--r--services/core/java/com/android/server/am/UserController.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java107
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java61
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java6
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java2
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java691
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java177
-rw-r--r--services/core/java/com/android/server/display/HysteresisLevels.java50
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/LightSensorController.java868
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java10
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java2
-rw-r--r--services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java22
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java658
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java48
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt57
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt130
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt432
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt71
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java82
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java1
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java14
228 files changed, 8433 insertions, 2814 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b897c1acd17f..88a3c6f97eb6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -304,6 +304,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
+ /** Need direct access to this for testing. */
+ private final FlexibilityController mFlexibilityController;
/** Needed to get next estimated launch time. */
private final PrefetchController mPrefetchController;
/** Needed to get remaining quota time. */
@@ -2701,17 +2703,16 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers = new ArrayList<StateController>();
mPrefetchController = new PrefetchController(this);
mControllers.add(mPrefetchController);
- final FlexibilityController flexibilityController =
- new FlexibilityController(this, mPrefetchController);
- mControllers.add(flexibilityController);
+ mFlexibilityController = new FlexibilityController(this, mPrefetchController);
+ mControllers.add(mFlexibilityController);
mConnectivityController =
- new ConnectivityController(this, flexibilityController);
+ new ConnectivityController(this, mFlexibilityController);
mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
- final IdleController idleController = new IdleController(this, flexibilityController);
+ final IdleController idleController = new IdleController(this, mFlexibilityController);
mControllers.add(idleController);
final BatteryController batteryController =
- new BatteryController(this, flexibilityController);
+ new BatteryController(this, mFlexibilityController);
mControllers.add(batteryController);
mStorageController = new StorageController(this);
mControllers.add(mStorageController);
@@ -5561,6 +5562,15 @@ public class JobSchedulerService extends com.android.server.SystemService
return 0;
}
+ // Shell command infrastructure: set flex policy
+ void setFlexPolicy(boolean override, int appliedConstraints) {
+ if (DEBUG) {
+ Slog.v(TAG, "setFlexPolicy(): " + override + "/" + appliedConstraints);
+ }
+
+ mFlexibilityController.setLocalPolicyForTesting(override, appliedConstraints);
+ }
+
void setMonitorBattery(boolean enabled) {
synchronized (mLock) {
mBatteryStateTracker.setMonitorBatteryLocked(enabled);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index af7b27e51e20..ac240ccff017 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -27,6 +27,7 @@ import android.os.Binder;
import android.os.UserHandle;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.job.controllers.JobStatus;
import java.io.PrintWriter;
@@ -59,6 +60,10 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return cancelJob(pw);
case "monitor-battery":
return monitorBattery(pw);
+ case "disable-flex-policy":
+ return disableFlexPolicy(pw);
+ case "enable-flex-policy":
+ return enableFlexPolicy(pw);
case "get-aconfig-flag-state":
return getAconfigFlagState(pw);
case "get-battery-seq":
@@ -91,6 +96,8 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return resetExecutionQuota(pw);
case "reset-schedule-quota":
return resetScheduleQuota(pw);
+ case "reset-flex-policy":
+ return resetFlexPolicy(pw);
case "stop":
return stop(pw);
case "trigger-dock-state":
@@ -346,6 +353,65 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int disableFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("disable flex policy");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(true, 0);
+ pw.println("Set flex policy to 0");
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private int enableFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("enable flex policy");
+
+ int enabled = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-o":
+ case "--option":
+ final String constraint = getNextArgRequired();
+ switch (constraint) {
+ case "battery-not-low":
+ enabled |= JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
+ break;
+ case "charging":
+ enabled |= JobStatus.CONSTRAINT_CHARGING;
+ break;
+ case "connectivity":
+ enabled |= JobStatus.CONSTRAINT_CONNECTIVITY;
+ break;
+ case "idle":
+ enabled |= JobStatus.CONSTRAINT_IDLE;
+ break;
+ default:
+ pw.println("Unsupported option: " + constraint);
+ return -1;
+ }
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(true, enabled);
+ pw.println("Set flex policy to " + enabled);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int getAconfigFlagState(PrintWriter pw) throws Exception {
checkPermission("get aconfig flag state", Manifest.permission.DUMP);
@@ -581,6 +647,19 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int resetFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("reset flex policy");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(false, 0);
+ pw.println("Reset flex policy to its default state");
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int resetExecutionQuota(PrintWriter pw) throws Exception {
checkPermission("reset execution quota");
@@ -773,6 +852,15 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
pw.println(" monitor-battery [on|off]");
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
pw.println(" on makes get-battery-seq useful.");
+ pw.println(" enable-flex-policy --option <option>");
+ pw.println(" Enable flex policy with the specified options. Supported options are");
+ pw.println(" battery-not-low, charging, connectivity, idle.");
+ pw.println(" Multiple enable options can be specified (e.g.");
+ pw.println(" enable-flex-policy --option battery-not-low --option charging");
+ pw.println(" disable-flex-policy");
+ pw.println(" Turn off flex policy so that it does not affect job execution.");
+ pw.println(" reset-flex-policy");
+ pw.println(" Resets the flex policy to its default state.");
pw.println(" get-aconfig-flag-state FULL_FLAG_NAME");
pw.println(" Return the state of the specified aconfig flag, if known. The flag name");
pw.println(" must be fully qualified.");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index ee9400fb8408..852b00b38347 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -328,6 +328,9 @@ public final class FlexibilityController extends StateController {
@GuardedBy("mLock")
private final ArraySet<String> mPackagesToCheck = new ArraySet<>();
+ @GuardedBy("mLock")
+ private boolean mLocalOverride;
+
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
super(service);
@@ -1923,6 +1926,27 @@ public final class FlexibilityController extends StateController {
}
}
+ /**
+ * If {@code override} is true, uses {@code appliedConstraints} for flex policy evaluation,
+ * overriding anything else that was set. If {@code override} is false, any previous calls
+ * will be discarded and the policy will be reset to the normal default policy.
+ */
+ public void setLocalPolicyForTesting(boolean override, int appliedConstraints) {
+ synchronized (mLock) {
+ final boolean recheckJobs = mLocalOverride != override
+ || mAppliedConstraints != appliedConstraints;
+ mLocalOverride = override;
+ if (mLocalOverride) {
+ mAppliedConstraints = appliedConstraints;
+ } else {
+ mAppliedConstraints = mFcConfig.APPLIED_CONSTRAINTS;
+ }
+ if (recheckJobs) {
+ mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
+ }
+ }
+ }
+
@Override
@GuardedBy("mLock")
public void dumpConstants(IndentingPrintWriter pw) {
@@ -1932,6 +1956,12 @@ public final class FlexibilityController extends StateController {
@Override
@GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+ if (mLocalOverride) {
+ pw.println("Local override active");
+ }
+ pw.print("Applied Flexible Constraints:");
+ JobStatus.dumpConstraints(pw, mAppliedConstraints);
+ pw.println();
pw.print("Satisfied Flexible Constraints:");
JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints);
pw.println();
diff --git a/core/api/current.txt b/core/api/current.txt
index b8c2d90e23c6..1cfc02531ebc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9242,140 +9242,140 @@ package android.app.people {
package android.app.slice {
- public final class Slice implements android.os.Parcelable {
- ctor protected Slice(android.os.Parcel);
- method public int describeContents();
- method public java.util.List<java.lang.String> getHints();
- method public java.util.List<android.app.slice.SliceItem> getItems();
- method @Nullable public android.app.slice.SliceSpec getSpec();
- method public android.net.Uri getUri();
- method public boolean isCallerNeeded();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
- field public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
- field public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
- field public static final String HINT_ACTIONS = "actions";
- field public static final String HINT_ERROR = "error";
- field public static final String HINT_HORIZONTAL = "horizontal";
- field public static final String HINT_KEYWORDS = "keywords";
- field public static final String HINT_LARGE = "large";
- field public static final String HINT_LAST_UPDATED = "last_updated";
- field public static final String HINT_LIST = "list";
- field public static final String HINT_LIST_ITEM = "list_item";
- field public static final String HINT_NO_TINT = "no_tint";
- field public static final String HINT_PARTIAL = "partial";
- field public static final String HINT_PERMISSION_REQUEST = "permission_request";
- field public static final String HINT_SEE_MORE = "see_more";
- field public static final String HINT_SELECTED = "selected";
- field public static final String HINT_SHORTCUT = "shortcut";
- field public static final String HINT_SUMMARY = "summary";
- field public static final String HINT_TITLE = "title";
- field public static final String HINT_TTL = "ttl";
- field public static final String SUBTYPE_COLOR = "color";
- field public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
- field public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
- field public static final String SUBTYPE_MAX = "max";
- field public static final String SUBTYPE_MESSAGE = "message";
- field public static final String SUBTYPE_MILLIS = "millis";
- field public static final String SUBTYPE_PRIORITY = "priority";
- field public static final String SUBTYPE_RANGE = "range";
- field public static final String SUBTYPE_SOURCE = "source";
- field public static final String SUBTYPE_TOGGLE = "toggle";
- field public static final String SUBTYPE_VALUE = "value";
- }
-
- public static class Slice.Builder {
- ctor public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec);
- ctor public Slice.Builder(@NonNull android.app.slice.Slice.Builder);
- method public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String);
- method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String);
- method public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice build();
- method public android.app.slice.Slice.Builder setCallerNeeded(boolean);
- }
-
- public final class SliceItem implements android.os.Parcelable {
- method public int describeContents();
- method public android.app.PendingIntent getAction();
- method public android.os.Bundle getBundle();
- method public String getFormat();
- method @NonNull public java.util.List<java.lang.String> getHints();
- method public android.graphics.drawable.Icon getIcon();
- method public int getInt();
- method public long getLong();
- method public android.app.RemoteInput getRemoteInput();
- method public android.app.slice.Slice getSlice();
- method public String getSubType();
- method public CharSequence getText();
- method public boolean hasHint(String);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
- field public static final String FORMAT_ACTION = "action";
- field public static final String FORMAT_BUNDLE = "bundle";
- field public static final String FORMAT_IMAGE = "image";
- field public static final String FORMAT_INT = "int";
- field public static final String FORMAT_LONG = "long";
- field public static final String FORMAT_REMOTE_INPUT = "input";
- field public static final String FORMAT_SLICE = "slice";
- field public static final String FORMAT_TEXT = "text";
- }
-
- public class SliceManager {
- method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method public int checkSlicePermission(@NonNull android.net.Uri, int, int);
- method @NonNull public java.util.List<android.net.Uri> getPinnedSlices();
- method @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
- method @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri);
- method public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri);
- method @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent);
- method public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri);
- method public void unpinSlice(@NonNull android.net.Uri);
- field public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
- field public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
- }
-
- public class SliceMetrics {
- ctor public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public void logHidden();
- method public void logTouch(int, @NonNull android.net.Uri);
- method public void logVisible();
- }
-
- public abstract class SliceProvider extends android.content.ContentProvider {
- ctor public SliceProvider(@NonNull java.lang.String...);
- ctor public SliceProvider();
- method public final int delete(android.net.Uri, String, String[]);
- method public final String getType(android.net.Uri);
- method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
- method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
- method @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
- method @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri);
- method @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent);
- method public void onSlicePinned(android.net.Uri);
- method public void onSliceUnpinned(android.net.Uri);
- method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String);
- method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal);
- method public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal);
- method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
- field public static final String SLICE_TYPE = "vnd.android.slice";
- }
-
- public final class SliceSpec implements android.os.Parcelable {
- ctor public SliceSpec(@NonNull String, int);
- method public boolean canRender(@NonNull android.app.slice.SliceSpec);
- method public int describeContents();
- method public int getRevision();
- method public String getType();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
+ @Deprecated public final class Slice implements android.os.Parcelable {
+ ctor @Deprecated protected Slice(android.os.Parcel);
+ method @Deprecated public int describeContents();
+ method @Deprecated public java.util.List<java.lang.String> getHints();
+ method @Deprecated public java.util.List<android.app.slice.SliceItem> getItems();
+ method @Deprecated @Nullable public android.app.slice.SliceSpec getSpec();
+ method @Deprecated public android.net.Uri getUri();
+ method @Deprecated public boolean isCallerNeeded();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+ field @Deprecated public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
+ field @Deprecated public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+ field @Deprecated public static final String HINT_ACTIONS = "actions";
+ field @Deprecated public static final String HINT_ERROR = "error";
+ field @Deprecated public static final String HINT_HORIZONTAL = "horizontal";
+ field @Deprecated public static final String HINT_KEYWORDS = "keywords";
+ field @Deprecated public static final String HINT_LARGE = "large";
+ field @Deprecated public static final String HINT_LAST_UPDATED = "last_updated";
+ field @Deprecated public static final String HINT_LIST = "list";
+ field @Deprecated public static final String HINT_LIST_ITEM = "list_item";
+ field @Deprecated public static final String HINT_NO_TINT = "no_tint";
+ field @Deprecated public static final String HINT_PARTIAL = "partial";
+ field @Deprecated public static final String HINT_PERMISSION_REQUEST = "permission_request";
+ field @Deprecated public static final String HINT_SEE_MORE = "see_more";
+ field @Deprecated public static final String HINT_SELECTED = "selected";
+ field @Deprecated public static final String HINT_SHORTCUT = "shortcut";
+ field @Deprecated public static final String HINT_SUMMARY = "summary";
+ field @Deprecated public static final String HINT_TITLE = "title";
+ field @Deprecated public static final String HINT_TTL = "ttl";
+ field @Deprecated public static final String SUBTYPE_COLOR = "color";
+ field @Deprecated public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
+ field @Deprecated public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
+ field @Deprecated public static final String SUBTYPE_MAX = "max";
+ field @Deprecated public static final String SUBTYPE_MESSAGE = "message";
+ field @Deprecated public static final String SUBTYPE_MILLIS = "millis";
+ field @Deprecated public static final String SUBTYPE_PRIORITY = "priority";
+ field @Deprecated public static final String SUBTYPE_RANGE = "range";
+ field @Deprecated public static final String SUBTYPE_SOURCE = "source";
+ field @Deprecated public static final String SUBTYPE_TOGGLE = "toggle";
+ field @Deprecated public static final String SUBTYPE_VALUE = "value";
+ }
+
+ @Deprecated public static class Slice.Builder {
+ ctor @Deprecated public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec);
+ ctor @Deprecated public Slice.Builder(@NonNull android.app.slice.Slice.Builder);
+ method @Deprecated public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String);
+ method @Deprecated public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String);
+ method @Deprecated public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice build();
+ method @Deprecated public android.app.slice.Slice.Builder setCallerNeeded(boolean);
+ }
+
+ @Deprecated public final class SliceItem implements android.os.Parcelable {
+ method @Deprecated public int describeContents();
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public android.os.Bundle getBundle();
+ method @Deprecated public String getFormat();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getHints();
+ method @Deprecated public android.graphics.drawable.Icon getIcon();
+ method @Deprecated public int getInt();
+ method @Deprecated public long getLong();
+ method @Deprecated public android.app.RemoteInput getRemoteInput();
+ method @Deprecated public android.app.slice.Slice getSlice();
+ method @Deprecated public String getSubType();
+ method @Deprecated public CharSequence getText();
+ method @Deprecated public boolean hasHint(String);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
+ field @Deprecated public static final String FORMAT_ACTION = "action";
+ field @Deprecated public static final String FORMAT_BUNDLE = "bundle";
+ field @Deprecated public static final String FORMAT_IMAGE = "image";
+ field @Deprecated public static final String FORMAT_INT = "int";
+ field @Deprecated public static final String FORMAT_LONG = "long";
+ field @Deprecated public static final String FORMAT_REMOTE_INPUT = "input";
+ field @Deprecated public static final String FORMAT_SLICE = "slice";
+ field @Deprecated public static final String FORMAT_TEXT = "text";
+ }
+
+ @Deprecated public class SliceManager {
+ method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated public int checkSlicePermission(@NonNull android.net.Uri, int, int);
+ method @Deprecated @NonNull public java.util.List<android.net.Uri> getPinnedSlices();
+ method @Deprecated @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
+ method @Deprecated @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri);
+ method @Deprecated public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri);
+ method @Deprecated @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent);
+ method @Deprecated public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri);
+ method @Deprecated public void unpinSlice(@NonNull android.net.Uri);
+ field @Deprecated public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
+ field @Deprecated public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+ }
+
+ @Deprecated public class SliceMetrics {
+ ctor @Deprecated public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @Deprecated public void logHidden();
+ method @Deprecated public void logTouch(int, @NonNull android.net.Uri);
+ method @Deprecated public void logVisible();
+ }
+
+ @Deprecated public abstract class SliceProvider extends android.content.ContentProvider {
+ ctor @Deprecated public SliceProvider(@NonNull java.lang.String...);
+ ctor @Deprecated public SliceProvider();
+ method @Deprecated public final int delete(android.net.Uri, String, String[]);
+ method @Deprecated public final String getType(android.net.Uri);
+ method @Deprecated public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+ method @Deprecated public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
+ method @Deprecated @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri);
+ method @Deprecated @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent);
+ method @Deprecated public void onSlicePinned(android.net.Uri);
+ method @Deprecated public void onSliceUnpinned(android.net.Uri);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal);
+ method @Deprecated public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
+ field @Deprecated public static final String SLICE_TYPE = "vnd.android.slice";
+ }
+
+ @Deprecated public final class SliceSpec implements android.os.Parcelable {
+ ctor @Deprecated public SliceSpec(@NonNull String, int);
+ method @Deprecated public boolean canRender(@NonNull android.app.slice.SliceSpec);
+ method @Deprecated public int describeContents();
+ method @Deprecated public int getRevision();
+ method @Deprecated public String getType();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
}
}
@@ -17982,7 +17982,6 @@ package android.graphics.fonts {
method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily build();
method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
- method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean canBuildVariableFamily();
}
public final class FontStyle {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5547028e489f..501203efc2ef 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2186,7 +2186,7 @@ package android.app.contextualsearch {
field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
}
- @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
diff --git a/core/java/android/accessibilityservice/BrailleDisplayController.java b/core/java/android/accessibilityservice/BrailleDisplayController.java
index 5282aa3fccf7..7334676c905d 100644
--- a/core/java/android/accessibilityservice/BrailleDisplayController.java
+++ b/core/java/android/accessibilityservice/BrailleDisplayController.java
@@ -305,4 +305,6 @@ public interface BrailleDisplayController {
@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
@TestApi
String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
+ /** @hide */
+ String TEST_BRAILLE_DISPLAY_NAME = "NAME";
}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 41b97d0ad5d2..97c2e43a1db6 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -42,6 +42,7 @@ per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ActivityThread.java = file:RESOURCES_OWNERS
# Alarm
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
diff --git a/core/java/android/app/RESOURCES_OWNERS b/core/java/android/app/RESOURCES_OWNERS
index 558280396348..fe37c0ccf5de 100644
--- a/core/java/android/app/RESOURCES_OWNERS
+++ b/core/java/android/app/RESOURCES_OWNERS
@@ -1,3 +1,5 @@
-rtmitchell@google.com
-toddke@google.com
+zyy@google.com
+jakmcbane@google.com
+branliu@google.com
+markpun@google.com
patb@google.com
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 4fa45be57a11..be0aaff95c96 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -174,6 +174,17 @@ flag {
}
flag {
+ name: "copy_account_with_retry_enabled"
+ namespace: "enterprise"
+ description: "Retry copy and remove account from personal to work profile in case of failure"
+ bug: "329424312"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
name: "esim_management_ux_enabled"
namespace: "enterprise"
description: "Enable UX changes for esim management"
diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java
index 0bbd1e546e5d..378193f8834c 100644
--- a/core/java/android/app/contextualsearch/CallbackToken.java
+++ b/core/java/android/app/contextualsearch/CallbackToken.java
@@ -51,6 +51,7 @@ public final class CallbackToken implements Parcelable {
private static final String TAG = CallbackToken.class.getSimpleName();
private final IBinder mToken;
+ private final Object mLock = new Object();
private boolean mTokenUsed = false;
public CallbackToken() {
@@ -75,10 +76,14 @@ public final class CallbackToken implements Parcelable {
public void getContextualSearchState(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken);
- if (mTokenUsed) {
+ boolean tokenUsed;
+ synchronized (mLock) {
+ tokenUsed = markUsedLocked();
+ }
+ if (tokenUsed) {
callback.onError(new IllegalAccessException("Token already used."));
+ return;
}
- mTokenUsed = true;
try {
// Get the service from the system server.
IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
@@ -96,6 +101,12 @@ public final class CallbackToken implements Parcelable {
}
}
+ private boolean markUsedLocked() {
+ boolean oldValue = mTokenUsed;
+ mTokenUsed = true;
+ return oldValue;
+ }
+
/**
* Return the token necessary for validating the caller of {@link #getContextualSearchState}.
*
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index a894a0e27b95..c080a6b423c5 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -43,7 +43,7 @@ import java.lang.annotation.RetentionPolicy;
*/
@SystemApi
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
-public class ContextualSearchManager {
+public final class ContextualSearchManager {
/**
* Key to get the entrypoint from the extras of the activity launched by contextual search.
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 475ee7a1ce3d..5514868a32f5 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -41,7 +41,12 @@ import java.util.Objects;
*
* <p>They are constructed using {@link Builder} in a tree structure
* that provides the OS some information about how the content should be displayed.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class Slice implements Parcelable {
/**
@@ -338,7 +343,12 @@ public final class Slice implements Parcelable {
/**
* A Builder used to construct {@link Slice}s
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class Builder {
private final Uri mUri;
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 2d6f4a6fd227..27c726dac4e5 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -53,7 +53,12 @@ import java.util.List;
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
* are defined on {@link Slice}.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class SliceItem implements Parcelable {
private static final String TAG = "SliceItem";
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 2e179d063b76..4fc2a0ce2286 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -59,7 +59,12 @@ import java.util.Set;
* Class to handle interactions with {@link Slice}s.
* <p>
* The SliceManager manages permissions and pinned state for slices.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
@SystemService(Context.SLICE_SERVICE)
public class SliceManager {
diff --git a/core/java/android/app/slice/SliceMetrics.java b/core/java/android/app/slice/SliceMetrics.java
index 746beaf939d9..abfe3a2fdc75 100644
--- a/core/java/android/app/slice/SliceMetrics.java
+++ b/core/java/android/app/slice/SliceMetrics.java
@@ -31,7 +31,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
* not need to reference this class.
*
* @see androidx.slice.widget.SliceView
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public class SliceMetrics {
private static final String TAG = "SliceMetrics";
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 42c3aa6aa0ea..4374550c3933 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -91,7 +91,12 @@ import java.util.Set;
* </pre>
*
* @see Slice
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public abstract class SliceProvider extends ContentProvider {
/**
* This is the Android platform's MIME type for a URI
diff --git a/core/java/android/app/slice/SliceSpec.java b/core/java/android/app/slice/SliceSpec.java
index a33234981059..078f552bb681 100644
--- a/core/java/android/app/slice/SliceSpec.java
+++ b/core/java/android/app/slice/SliceSpec.java
@@ -38,7 +38,12 @@ import android.os.Parcelable;
*
* @see Slice
* @see SliceProvider#onBindSlice(Uri, Set)
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class SliceSpec implements Parcelable {
private final String mType;
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 689e343bcbc6..64fc4c29db90 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -333,14 +333,12 @@ public final class DeviceState {
private final ArraySet<@PhysicalDeviceStateProperties Integer> mPhysicalProperties;
private Configuration(int identifier, @NonNull String name,
- @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties,
- @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+ @NonNull ArraySet<@SystemDeviceStateProperties Integer> systemProperties,
+ @NonNull ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties) {
mIdentifier = identifier;
mName = name;
- mSystemProperties = new ArraySet<@SystemDeviceStateProperties Integer>(
- systemProperties);
- mPhysicalProperties = new ArraySet<@PhysicalDeviceStateProperties Integer>(
- physicalProperties);
+ mSystemProperties = systemProperties;
+ mPhysicalProperties = physicalProperties;
}
/** Returns the unique identifier for the device state. */
@@ -479,8 +477,8 @@ public final class DeviceState {
*/
@NonNull
public DeviceState.Configuration build() {
- return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
- mPhysicalProperties);
+ return new DeviceState.Configuration(mIdentifier, mName,
+ new ArraySet<>(mSystemProperties), new ArraySet<>(mPhysicalProperties));
}
}
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java
index c319c893aaab..28561ec37ee6 100644
--- a/core/java/android/hardware/devicestate/DeviceStateInfo.java
+++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java
@@ -25,7 +25,6 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -77,9 +76,11 @@ public final class DeviceStateInfo implements Parcelable {
* NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
* supplied parameters.
*/
- public DeviceStateInfo(@NonNull List<DeviceState> supportedStates, DeviceState baseState,
+ // Using the specific types to avoid virtual method calls in binder transactions
+ @SuppressWarnings("NonApiType")
+ public DeviceStateInfo(@NonNull ArrayList<DeviceState> supportedStates, DeviceState baseState,
DeviceState state) {
- this.supportedStates = new ArrayList<>(supportedStates);
+ this.supportedStates = supportedStates;
this.baseState = baseState;
this.currentState = state;
}
@@ -89,13 +90,13 @@ public final class DeviceStateInfo implements Parcelable {
* the fields of the returned instance.
*/
public DeviceStateInfo(@NonNull DeviceStateInfo info) {
- this(List.copyOf(info.supportedStates), info.baseState, info.currentState);
+ this(new ArrayList<>(info.supportedStates), info.baseState, info.currentState);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) return true;
- if (other == null || getClass() != other.getClass()) return false;
+ if (other == null || (getClass() != other.getClass())) return false;
DeviceStateInfo that = (DeviceStateInfo) other;
return baseState.equals(that.baseState)
&& currentState.equals(that.currentState)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dd548c6f9da8..a0733d445257 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -110,6 +110,7 @@ import static android.view.accessibility.Flags.reduceWindowContentChangedEventTh
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -1148,6 +1149,7 @@ public final class ViewRootImpl implements ViewParent,
private String mLargestViewTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
@@ -1155,6 +1157,8 @@ public final class ViewRootImpl implements ViewParent,
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
+ sToolkitFrameRateFunctionEnablingReadOnlyFlagValue =
+ toolkitFrameRateFunctionEnablingReadOnly();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -12788,7 +12792,9 @@ public final class ViewRootImpl implements ViewParent,
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+ }
return false;
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 06598b3dfdbd..1d4d18b120c9 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -86,4 +86,12 @@ flag {
description: "Feature flag for suppressing boost on typing"
bug: "239979904"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_function_enabling_read_only"
+ namespace: "toolkit"
+ description: "Feature flag to enable the functionality of the dVRR feature"
+ bug: "239979904"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f54ef3868f53..ab6b5122ea05 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
@@ -5098,6 +5100,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
boolean more = scroller.computeScrollOffset();
final int y = scroller.getCurrY();
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
// Flip sign to convert finger direction to list items direction
// (e.g. finger moving down means list is moving towards the top)
int delta = consumeFlingInStretch(mLastFlingY - y);
@@ -5192,6 +5199,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
invalidate();
postOnAnimation(this);
}
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
} else {
endFling();
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 7fbec67ec4e9..fa0dab09a8b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -86,3 +86,10 @@ flag {
description: "Whether the blurred letterbox wallpaper background is enabled by default"
bug: "297195682"
}
+
+flag {
+ name: "enable_compatui_sysui_launcher"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Enables sysui animation for user aspect ratio button"
+ bug: "300357441"
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
index ce8ca0d781e4..2d365368a013 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -21,6 +21,11 @@ import java.util.List;
* Interface for the companion operations
*/
public interface CompanionOperation {
+ /**
+ * Read, create and add instance to operations
+ * @param buffer data to read to create operation
+ * @param operations command is to be added
+ */
void read(WireBuffer buffer, List<Operation> operations);
// Debugging / Documentation utility functions
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 0e4c7430afb5..55f2dee95a34 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -331,6 +331,7 @@ public class CoreDocument {
public void initFromBuffer(RemoteComposeBuffer buffer) {
mOperations = new ArrayList<Operation>();
buffer.inflateFromBuffer(mOperations);
+ mBuffer = buffer;
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index b8bb1f0f3519..54b277a2ac58 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -17,8 +17,29 @@ package com.android.internal.widget.remotecompose.core;
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawArc;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
+import com.android.internal.widget.remotecompose.core.operations.DrawLine;
+import com.android.internal.widget.remotecompose.core.operations.DrawOval;
+import com.android.internal.widget.remotecompose.core.operations.DrawPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
+import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
+import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
import com.android.internal.widget.remotecompose.core.operations.TextData;
@@ -48,7 +69,30 @@ public class Operations {
public static final int DATA_BITMAP = 101;
public static final int DATA_TEXT = 102;
+/////////////////////////////=====================
+ public static final int CLIP_PATH = 38;
+ public static final int CLIP_RECT = 39;
+ public static final int PAINT_VALUES = 40;
+ public static final int DRAW_RECT = 42;
+ public static final int DRAW_TEXT_RUN = 43;
+ public static final int DRAW_CIRCLE = 46;
+ public static final int DRAW_LINE = 47;
+ public static final int DRAW_ROUND_RECT = 51;
+ public static final int DRAW_ARC = 52;
+ public static final int DRAW_TEXT_ON_PATH = 53;
+ public static final int DRAW_OVAL = 56;
+ public static final int DATA_PATH = 123;
+ public static final int DRAW_PATH = 124;
+ public static final int DRAW_TWEEN_PATH = 125;
+ public static final int MATRIX_SCALE = 126;
+ public static final int MATRIX_TRANSLATE = 127;
+ public static final int MATRIX_SKEW = 128;
+ public static final int MATRIX_ROTATE = 129;
+ public static final int MATRIX_SAVE = 130;
+ public static final int MATRIX_RESTORE = 131;
+ public static final int MATRIX_SET = 132;
+ /////////////////////////////////////////======================
public static IntMap<CompanionOperation> map = new IntMap<>();
static {
@@ -60,6 +104,29 @@ public class Operations {
map.put(CLICK_AREA, ClickArea.COMPANION);
map.put(ROOT_CONTENT_BEHAVIOR, RootContentBehavior.COMPANION);
map.put(ROOT_CONTENT_DESCRIPTION, RootContentDescription.COMPANION);
+
+ map.put(DRAW_ARC, DrawArc.COMPANION);
+ map.put(DRAW_BITMAP, DrawBitmap.COMPANION);
+ map.put(DRAW_CIRCLE, DrawCircle.COMPANION);
+ map.put(DRAW_LINE, DrawLine.COMPANION);
+ map.put(DRAW_OVAL, DrawOval.COMPANION);
+ map.put(DRAW_PATH, DrawPath.COMPANION);
+ map.put(DRAW_RECT, DrawRect.COMPANION);
+ map.put(DRAW_ROUND_RECT, DrawRoundRect.COMPANION);
+ map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath.COMPANION);
+ map.put(DRAW_TEXT_RUN, DrawTextRun.COMPANION);
+ map.put(DRAW_TWEEN_PATH, DrawTweenPath.COMPANION);
+ map.put(DATA_PATH, PathData.COMPANION);
+ map.put(PAINT_VALUES, PaintData.COMPANION);
+ map.put(MATRIX_RESTORE, MatrixRestore.COMPANION);
+ map.put(MATRIX_ROTATE, MatrixRotate.COMPANION);
+ map.put(MATRIX_SAVE, MatrixSave.COMPANION);
+ map.put(MATRIX_SCALE, MatrixScale.COMPANION);
+ map.put(MATRIX_SKEW, MatrixSkew.COMPANION);
+ map.put(MATRIX_TRANSLATE, MatrixTranslate.COMPANION);
+ map.put(CLIP_PATH, ClipPath.COMPANION);
+ map.put(CLIP_RECT, ClipRect.COMPANION);
+
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 6999cdeadfd7..eece8ad52b60 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
/**
* Specify an abstract paint context used by RemoteCompose commands to draw
*/
@@ -30,11 +32,74 @@ public abstract class PaintContext {
}
public abstract void drawBitmap(int imageId,
- int srcLeft, int srcTop, int srcRight, int srcBottom,
- int dstLeft, int dstTop, int dstRight, int dstBottom,
- int cdId);
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId);
public abstract void scale(float scaleX, float scaleY);
+
public abstract void translate(float translateX, float translateY);
+
+ public abstract void drawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle);
+
+ public abstract void drawBitmap(int id, float left, float top, float right, float bottom);
+
+ public abstract void drawCircle(float centerX, float centerY, float radius);
+
+ public abstract void drawLine(float x1, float y1, float x2, float y2);
+
+ public abstract void drawOval(float left, float top, float right, float bottom);
+
+ public abstract void drawPath(int id, float start, float end);
+
+ public abstract void drawRect(float left, float top, float right, float bottom);
+
+ public abstract void drawRoundRect(float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY);
+
+ public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset);
+
+ public abstract void drawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl);
+
+ public abstract void drawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop);
+
+ public abstract void applyPaint(PaintBundle mPaintData);
+
+ public abstract void mtrixScale(float scaleX, float scaleY, float centerX, float centerY);
+
+ public abstract void matrixTranslate(float translateX, float translateY);
+
+ public abstract void matrixSkew(float skewX, float skewY);
+
+ public abstract void matrixRotate(float rotate, float pivotX, float pivotY);
+
+ public abstract void matrixSave();
+
+ public abstract void matrixRestore();
+
+ public abstract void clipRect(float left, float top, float right, float bottom);
+
+ public abstract void clipPath(int pathId, int regionOp);
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index abda0c0d9a0c..903dab49cd06 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -20,5 +20,8 @@ package com.android.internal.widget.remotecompose.core;
*/
public interface Platform {
byte[] imageToByteArray(Object image);
+ int getImageWidth(Object image);
+ int getImageHeight(Object image);
+ float[] pathToFloatArray(Object image);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index c34730fdef04..c2e81318c09a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -17,12 +17,34 @@ package com.android.internal.widget.remotecompose.core;
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawArc;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
+import com.android.internal.widget.remotecompose.core.operations.DrawLine;
+import com.android.internal.widget.remotecompose.core.operations.DrawOval;
+import com.android.internal.widget.remotecompose.core.operations.DrawPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
+import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
+import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import java.io.File;
import java.io.FileInputStream;
@@ -82,10 +104,10 @@ public class RemoteComposeBuffer {
/**
* Insert a header
*
- * @param width the width of the document in pixels
- * @param height the height of the document in pixels
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
* @param contentDescription content description of the document
- * @param capabilities bitmask indicating needed capabilities (unused for now)
+ * @param capabilities bitmask indicating needed capabilities (unused for now)
*/
public void header(int width, int height, String contentDescription, long capabilities) {
Header.COMPANION.apply(mBuffer, width, height, capabilities);
@@ -99,8 +121,8 @@ public class RemoteComposeBuffer {
/**
* Insert a header
*
- * @param width the width of the document in pixels
- * @param height the height of the document in pixels
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
* @param contentDescription content description of the document
*/
public void header(int width, int height, String contentDescription) {
@@ -111,7 +133,7 @@ public class RemoteComposeBuffer {
* Insert a bitmap
*
* @param image an opaque image that we'll add to the buffer
- * @param imageWidth the width of the image
+ * @param imageWidth the width of the image
* @param imageHeight the height of the image
* @param srcLeft left coordinate of the source area
* @param srcTop top coordinate of the source area
@@ -161,13 +183,13 @@ public class RemoteComposeBuffer {
/**
* Add a click area to the document
*
- * @param id the id of the click area, reported in the click listener callback
+ * @param id the id of the click area, reported in the click listener callback
* @param contentDescription the content description of that click area (accessibility)
- * @param left left coordinate of the area bounds
- * @param top top coordinate of the area bounds
- * @param right right coordinate of the area bounds
- * @param bottom bottom coordinate of the area bounds
- * @param metadata associated metadata, user-provided
+ * @param left left coordinate of the area bounds
+ * @param top top coordinate of the area bounds
+ * @param right right coordinate of the area bounds
+ * @param bottom bottom coordinate of the area bounds
+ * @param metadata associated metadata, user-provided
*/
public void addClickArea(
int id,
@@ -193,35 +215,294 @@ public class RemoteComposeBuffer {
/**
* Sets the way the player handles the content
*
- * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
* @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
- * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
- * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
- * the LAYOUT modes are:
- * - LAYOUT_MATCH_PARENT
- * - LAYOUT_WRAP_CONTENT
- * or adding an horizontal mode and a vertical mode:
- * - LAYOUT_HORIZONTAL_MATCH_PARENT
- * - LAYOUT_HORIZONTAL_WRAP_CONTENT
- * - LAYOUT_HORIZONTAL_FIXED
- * - LAYOUT_VERTICAL_MATCH_PARENT
- * - LAYOUT_VERTICAL_WRAP_CONTENT
- * - LAYOUT_VERTICAL_FIXED
- * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
*/
public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode);
}
+ /**
+ * add Drawing the specified arc, which will be scaled to fit inside the specified oval.
+ * <br>
+ * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+ * 360.
+ * <br>
+ * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+ * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+ * negative, the sweep angle is treated as sweep angle modulo 360
+ * <br>
+ * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+ * degrees (3 o'clock on a watch.)
+ * <br>
+ *
+ * @param left left coordinate of oval used to define the shape and size of the arc
+ * @param top top coordinate of oval used to define the shape and size of the arc
+ * @param right right coordinate of oval used to define the shape and size of the arc
+ * @param bottom bottom coordinate of oval used to define the shape and size of the arc
+ * @param startAngle Starting angle (in degrees) where the arc begins
+ * @param sweepAngle Sweep angle (in degrees) measured clockwise
+ */
+ public void addDrawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ DrawArc.COMPANION.apply(mBuffer, left, top, right, bottom, startAngle, sweepAngle);
+ }
+
+ /**
+ * @param image The bitmap to be drawn
+ * @param left left coordinate of rectangle that the bitmap will be to fit into
+ * @param top top coordinate of rectangle that the bitmap will be to fit into
+ * @param right right coordinate of rectangle that the bitmap will be to fit into
+ * @param bottom bottom coordinate of rectangle that the bitmap will be to fit into
+ * @param contentDescription content description of the image
+ */
+ public void addDrawBitmap(Object image,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ String contentDescription) {
+ int imageId = mRemoteComposeState.dataGetId(image);
+ if (imageId == -1) {
+ imageId = mRemoteComposeState.cache(image);
+ byte[] data = mPlatform.imageToByteArray(image);
+ int imageWidth = mPlatform.getImageWidth(image);
+ int imageHeight = mPlatform.getImageHeight(image);
+
+ BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+ }
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ }
+ DrawBitmap.COMPANION.apply(
+ mBuffer, imageId, left, top, right, bottom, contentDescriptionId
+ );
+ }
+
+ /**
+ * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be
+ * drawn.
+ *
+ * @param centerX The x-coordinate of the center of the circle to be drawn
+ * @param centerY The y-coordinate of the center of the circle to be drawn
+ * @param radius The radius of the circle to be drawn
+ */
+ public void addDrawCircle(float centerX, float centerY, float radius) {
+ DrawCircle.COMPANION.apply(mBuffer, centerX, centerY, radius);
+ }
+
+ /**
+ * Draw a line segment with the specified start and stop x,y coordinates, using the specified
+ * paint.
+ *
+ * @param x1 The x-coordinate of the start point of the line
+ * @param y1 The y-coordinate of the start point of the line
+ * @param x2 The x-coordinate of the end point of the line
+ * @param y2 The y-coordinate of the end point of the line
+ */
+ public void addDrawLine(float x1, float y1, float x2, float y2) {
+ DrawLine.COMPANION.apply(mBuffer, x1, y1, x2, y2);
+ }
+
+ /**
+ * Draw the specified oval using the specified paint.
+ *
+ * @param left left coordinate of oval
+ * @param top top coordinate of oval
+ * @param right right coordinate of oval
+ * @param bottom bottom coordinate of oval
+ */
+ public void addDrawOval(float left, float top, float right, float bottom) {
+ DrawOval.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+ /**
+ * Draw the specified path
+ * <p>
+ * Note: path objects are not immutable
+ * modifying them and calling this will not change the drawing
+ *
+ * @param path The path to be drawn
+ */
+ public void addDrawPath(Object path) {
+ int id = mRemoteComposeState.dataGetId(path);
+ if (id == -1) { // never been seen before
+ id = addPathData(path);
+ }
+ addDrawPath(id);
+ }
+
+
+ /**
+ * Draw the specified path
+ *
+ * @param pathId
+ */
+ public void addDrawPath(int pathId) {
+ DrawPath.COMPANION.apply(mBuffer, pathId);
+ }
+
+ /**
+ * Draw the specified Rect
+ *
+ * @param left left coordinate of rectangle to be drawn
+ * @param top top coordinate of rectangle to be drawn
+ * @param right right coordinate of rectangle to be drawn
+ * @param bottom bottom coordinate of rectangle to be drawn
+ */
+ public void addDrawRect(float left, float top, float right, float bottom) {
+ DrawRect.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+ /**
+ * Draw the specified round-rect
+ *
+ * @param left left coordinate of rectangle to be drawn
+ * @param top left coordinate of rectangle to be drawn
+ * @param right left coordinate of rectangle to be drawn
+ * @param bottom left coordinate of rectangle to be drawn
+ * @param radiusX The x-radius of the oval used to round the corners
+ * @param radiusY The y-radius of the oval used to round the corners
+ */
+ public void addDrawRoundRect(float left, float top, float right, float bottom,
+ float radiusX, float radiusY) {
+ DrawRoundRect.COMPANION.apply(mBuffer, left, top, right, bottom, radiusX, radiusY);
+ }
+
+ /**
+ * Draw the text, with origin at (x,y) along the specified path.
+ *
+ * @param text The text to be drawn
+ * @param path The path the text should follow for its baseline
+ * @param hOffset The distance along the path to add to the text's starting position
+ * @param vOffset The distance above(-) or below(+) the path to position the text
+ */
+ public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) {
+ int pathId = mRemoteComposeState.dataGetId(path);
+ if (pathId == -1) { // never been seen before
+ pathId = addPathData(path);
+ }
+ int textId = addText(text);
+ DrawTextOnPath.COMPANION.apply(mBuffer, textId, pathId, hOffset, vOffset);
+ }
+
+ /**
+ * Draw the text, with origin at (x,y). The origin is interpreted
+ * based on the Align setting in the paint.
+ *
+ * @param text The text to be drawn
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param contextStart
+ * @param contextEnd
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
+ * @param rtl Draw RTTL
+ */
+ public void addDrawTextRun(String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ int textId = addText(text);
+ DrawTextRun.COMPANION.apply(
+ mBuffer, textId, start, end,
+ contextStart, contextEnd, x, y, rtl);
+ }
+
+ /**
+ * draw an interpolation between two paths that have the same pattern
+ * <p>
+ * Warning paths objects are not immutable and this is not taken into consideration
+ *
+ * @param path1 The path1 to be drawn between
+ * @param path2 The path2 to be drawn between
+ * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
+ * @param start The start of the subrange of paths to draw 0 = start form start 0.5 is half way
+ * @param stop The end of the subrange of paths to draw 1 = end at the end 0.5 is end half way
+ */
+ public void addDrawTweenPath(Object path1,
+ Object path2,
+ float tween,
+ float start,
+ float stop) {
+ int path1Id = mRemoteComposeState.dataGetId(path1);
+ if (path1Id == -1) { // never been seen before
+ path1Id = addPathData(path1);
+ }
+ int path2Id = mRemoteComposeState.dataGetId(path2);
+ if (path2Id == -1) { // never been seen before
+ path2Id = addPathData(path2);
+ }
+ addDrawTweenPath(path1Id, path2Id, tween, start, stop);
+ }
+
+ /**
+ * draw an interpolation between two paths that have the same pattern
+ *
+ * @param path1Id The path1 to be drawn between
+ * @param path2Id The path2 to be drawn between
+ * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
+ * @param start The start of the subrange of paths to draw 0 = start form start .5 is 1/2 way
+ * @param stop The end of the subrange of paths to draw 1 = end at the end .5 is end 1/2 way
+ */
+ public void addDrawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ DrawTweenPath.COMPANION.apply(
+ mBuffer, path1Id, path2Id,
+ tween, start, stop);
+ }
+
+ /**
+ * Add a path object
+ *
+ * @param path
+ * @return the id of the path on the wire
+ */
+ public int addPathData(Object path) {
+ float[] pathData = mPlatform.pathToFloatArray(path);
+ int id = mRemoteComposeState.cache(path);
+ PathData.COMPANION.apply(mBuffer, id, pathData);
+ return id;
+ }
+
+ public void addPaint(PaintBundle paint) {
+ PaintData.COMPANION.apply(mBuffer, paint);
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
public void inflateFromBuffer(ArrayList<Operation> operations) {
mBuffer.setIndex(0);
while (mBuffer.available()) {
int opId = mBuffer.readByte();
+ System.out.println(">>> " + opId);
CompanionOperation operation = Operations.map.get(opId);
if (operation == null) {
- throw new RuntimeException("Unknown operation encountered");
+ throw new RuntimeException("Unknown operation encountered " + opId);
}
operation.read(mBuffer, operations);
}
@@ -259,7 +540,7 @@ public class RemoteComposeBuffer {
}
public static RemoteComposeBuffer fromInputStream(InputStream inputStream,
- RemoteComposeState remoteComposeState) {
+ RemoteComposeState remoteComposeState) {
RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
read(inputStream, buffer);
return buffer;
@@ -318,5 +599,86 @@ public class RemoteComposeBuffer {
}
}
+ /**
+ * add a Pre-concat the current matrix with the specified skew.
+ *
+ * @param skewX The amount to skew in X
+ * @param skewY The amount to skew in Y
+ */
+ public void addMatrixSkew(float skewX, float skewY) {
+ MatrixSkew.COMPANION.apply(mBuffer, skewX, skewY);
+ }
+
+ /**
+ * This call balances a previous call to save(), and is used to remove all
+ * modifications to the matrix/clip state since the last save call.
+ * Do not call restore() more times than save() was called.
+ */
+ public void addMatrixRestore() {
+ MatrixRestore.COMPANION.apply(mBuffer);
+ }
+
+ /**
+ * Add a saves the current matrix and clip onto a private stack.
+ * <p>
+ * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+ * clipPath will all operate as usual, but when the balancing call to
+ * restore() is made, those calls will be forgotten, and the settings that
+ * existed before the save() will be reinstated.
+ */
+ public void addMatrixSave() {
+ MatrixSave.COMPANION.apply(mBuffer);
+ }
+
+ /**
+ * add a pre-concat the current matrix with the specified rotation.
+ *
+ * @param angle The amount to rotate, in degrees
+ * @param centerX The x-coord for the pivot point (unchanged by the rotation)
+ * @param centerY The y-coord for the pivot point (unchanged by the rotation)
+ */
+ public void addMatrixRotate(float angle, float centerX, float centerY) {
+ MatrixRotate.COMPANION.apply(mBuffer, angle, centerX, centerY);
+ }
+
+ /**
+ * add a Pre-concat to the current matrix with the specified translation
+ *
+ * @param dx The distance to translate in X
+ * @param dy The distance to translate in Y
+ */
+ public void addMatrixTranslate(float dx, float dy) {
+ MatrixTranslate.COMPANION.apply(mBuffer, dx, dy);
+ }
+
+ /**
+ * Add a pre-concat of the current matrix with the specified scale.
+ *
+ * @param scaleX The amount to scale in X
+ * @param scaleY The amount to scale in Y
+ */
+ public void addMatrixScale(float scaleX, float scaleY) {
+ MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN);
+ }
+
+ /**
+ * Add a pre-concat of the current matrix with the specified scale.
+ *
+ * @param scaleX The amount to scale in X
+ * @param scaleY The amount to scale in Y
+ * @param centerX The x-coord for the pivot point (unchanged by the scale)
+ * @param centerY The y-coord for the pivot point (unchanged by the scale)
+ */
+ public void addMatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
+ MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY);
+ }
+
+ public void addClipPath(int pathId) {
+ ClipPath.COMPANION.apply(mBuffer, pathId);
+ }
+
+ public void addClipRect(float left, float top, float right, float bottom) {
+ ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 1b7c6fd0f218..d16cbc5a1a16 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -37,6 +37,8 @@ public abstract class RemoteContext {
public float mWidth = 0f;
public float mHeight = 0f;
+ public abstract void loadPathData(int instanceId, float[] floatPath);
+
/**
* The context can be used in a few different mode, allowing operations to skip being executed:
* - UNSET : all operations will get executed
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 7c9fda5c6631..fc3202e2160d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -37,7 +37,7 @@ public class WireBuffer {
this(BUFFER_SIZE);
}
- public void resize(int need) {
+ private void resize(int need) {
if (mSize + need >= mMaxSize) {
mMaxSize = Math.max(mMaxSize * 2, mSize + need);
mBuffer = Arrays.copyOf(mBuffer, mMaxSize);
@@ -120,7 +120,7 @@ public class WireBuffer {
}
public int readByte() {
- byte value = mBuffer[mIndex];
+ int value = 0xFF & mBuffer[mIndex];
mIndex++;
return value;
}
@@ -130,6 +130,14 @@ public class WireBuffer {
int v2 = (mBuffer[mIndex++] & 0xFF) << 0;
return v1 + v2;
}
+ public int peekInt() {
+ int tmp = mIndex;
+ int v1 = (mBuffer[tmp++] & 0xFF) << 24;
+ int v2 = (mBuffer[tmp++] & 0xFF) << 16;
+ int v3 = (mBuffer[tmp++] & 0xFF) << 8;
+ int v4 = (mBuffer[tmp++] & 0xFF) << 0;
+ return v1 + v2 + v3 + v4;
+ }
public int readInt() {
int v1 = (mBuffer[mIndex++] & 0xFF) << 24;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
new file mode 100644
index 000000000000..8d4a787148ef
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.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 com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class ClipPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mId;
+ int mRegionOp;
+
+ public ClipPath(int pathId, int regionOp) {
+ mId = pathId;
+ mRegionOp = regionOp;
+ }
+
+ public static final int REPLACE = Companion.PATH_CLIP_REPLACE;
+ public static final int DIFFERENCE = Companion.PATH_CLIP_DIFFERENCE;
+ public static final int INTERSECT = Companion.PATH_CLIP_INTERSECT;
+ public static final int UNION = Companion.PATH_CLIP_UNION;
+ public static final int XOR = Companion.PATH_CLIP_XOR;
+ public static final int REVERSE_DIFFERENCE = Companion.PATH_CLIP_REVERSE_DIFFERENCE;
+ public static final int UNDEFINED = Companion.PATH_CLIP_UNDEFINED;
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId);
+ }
+
+ @Override
+ public String toString() {
+ return "ClipPath " + mId + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ public static final int PATH_CLIP_REPLACE = 0;
+ public static final int PATH_CLIP_DIFFERENCE = 1;
+ public static final int PATH_CLIP_INTERSECT = 2;
+ public static final int PATH_CLIP_UNION = 3;
+ public static final int PATH_CLIP_XOR = 4;
+ public static final int PATH_CLIP_REVERSE_DIFFERENCE = 5;
+ public static final int PATH_CLIP_UNDEFINED = 6;
+
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int pack = buffer.readInt();
+ int id = pack & 0xFFFFF;
+ int regionOp = pack >> 24;
+ ClipPath op = new ClipPath(id, regionOp);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "ClipPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.CLIP_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id) {
+ buffer.start(Operations.CLIP_PATH);
+ buffer.writeInt(id);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipPath(mId, mRegionOp);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
new file mode 100644
index 000000000000..803618a91737
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class ClipRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public ClipRect(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "ClipRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ ClipRect op = new ClipRect(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "ClipRect";
+ }
+
+ @Override
+ public int id() {
+ return Operations.CLIP_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.CLIP_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipRect(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
new file mode 100644
index 000000000000..e829975cd39b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawArc extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ float mStartAngle;
+ float mSweepAngle;
+
+ public DrawArc(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mStartAngle = startAngle;
+ mSweepAngle = sweepAngle;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mStartAngle,
+ mSweepAngle);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + " "
+ + "- " + mStartAngle + " " + mSweepAngle + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ float mStartAngle = buffer.readFloat();
+ float mSweepAngle = buffer.readFloat();
+ DrawArc op = new DrawArc(sLeft, srcTop, srcRight, srcBottom,
+ mStartAngle, mSweepAngle);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawArc";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_ARC;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ buffer.start(Operations.DRAW_ARC);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeFloat(startAngle);
+ buffer.writeFloat(sweepAngle);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawArc(mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mStartAngle,
+ mSweepAngle);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
new file mode 100644
index 000000000000..2e971f533ed2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.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 com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawBitmap extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ int mId;
+ int mDescriptionId = 0;
+
+ public DrawBitmap(
+ int imageId,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int descriptionId) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mId = imageId;
+ mDescriptionId = descriptionId;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawBitmap (desc=" + mDescriptionId + ")" + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int id = buffer.readInt();
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ int discriptionId = buffer.readInt();
+
+ DrawBitmap op = new DrawBitmap(id, sLeft, srcTop, srcRight, srcBottom, discriptionId);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_BITMAP;
+ }
+
+ public void apply(WireBuffer buffer,
+ int id,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int descriptionId) {
+ buffer.start(Operations.DRAW_BITMAP);
+ buffer.writeInt(id);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeInt(descriptionId);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawBitmap(mId, mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 3fbdf94427d1..c2a56e7256d6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -76,7 +76,8 @@ public class DrawBitmapInt extends PaintOperation {
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
@@ -89,9 +90,9 @@ public class DrawBitmapInt extends PaintOperation {
}
public void apply(WireBuffer buffer, int imageId,
- int srcLeft, int srcTop, int srcRight, int srcBottom,
- int dstLeft, int dstTop, int dstRight, int dstBottom,
- int cdId) {
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId) {
buffer.start(Operations.DRAW_BITMAP_INT);
buffer.writeInt(imageId);
buffer.writeInt(srcLeft);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
new file mode 100644
index 000000000000..9ce754da1b1b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawCircle extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mCenterX;
+ float mCenterY;
+ float mRadius;
+
+ public DrawCircle(float centerX, float centerY, float radius) {
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mRadius = radius;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mCenterX,
+ mCenterY,
+ mRadius);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float centerX = buffer.readFloat();
+ float centerY = buffer.readFloat();
+ float radius = buffer.readFloat();
+
+ DrawCircle op = new DrawCircle(centerX, centerY, radius);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public int id() {
+ return 0;
+ }
+
+ public void apply(WireBuffer buffer, float centerX, float centerY, float radius) {
+ buffer.start(Operations.DRAW_CIRCLE);
+ buffer.writeFloat(centerX);
+ buffer.writeFloat(centerY);
+ buffer.writeFloat(radius);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawCircle(mCenterX,
+ mCenterY,
+ mRadius);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
new file mode 100644
index 000000000000..c7a8315a2274
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawLine extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mX1;
+ float mY1;
+ float mX2;
+ float mY2;
+
+ public DrawLine(
+ float x1,
+ float y1,
+ float x2,
+ float y2) {
+ mX1 = x1;
+ mY1 = y1;
+ mX2 = x2;
+ mY2 = y2;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX1,
+ mY1,
+ mX2,
+ mY2);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mX1 + " " + mY1
+ + " " + mX2 + " " + mY2 + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x1 = buffer.readFloat();
+ float y1 = buffer.readFloat();
+ float x2 = buffer.readFloat();
+ float y2 = buffer.readFloat();
+
+ DrawLine op = new DrawLine(x1, y1, x2, y2);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawLine";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_LINE;
+ }
+
+ public void apply(WireBuffer buffer,
+ float x1,
+ float y1,
+ float x2,
+ float y2) {
+ buffer.start(Operations.DRAW_LINE);
+ buffer.writeFloat(x1);
+ buffer.writeFloat(y1);
+ buffer.writeFloat(x2);
+ buffer.writeFloat(y2);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawLine(mX1,
+ mY1,
+ mX2,
+ mY2);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
new file mode 100644
index 000000000000..714375335cb2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawOval extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+
+ public DrawOval(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawOval " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ DrawOval op = new DrawOval(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_OVAL;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.DRAW_OVAL);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawOval(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
new file mode 100644
index 000000000000..7b8a9e95d9cb
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mId;
+ float mStart = 0;
+ float mEnd = 1;
+
+ public DrawPath(int pathId) {
+ mId = pathId;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawPath " + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int id = buffer.readInt();
+ DrawPath op = new DrawPath(id);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id) {
+ buffer.start(Operations.DRAW_PATH);
+ buffer.writeInt(id);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawPath(mId, mStart, mEnd);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
new file mode 100644
index 000000000000..4775241faa6f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public DrawRect(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ DrawRect op = new DrawRect(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawRect";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.DRAW_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawRect(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
new file mode 100644
index 000000000000..8da16e768b7f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawRoundRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ float mRadiusX;
+ float mRadiusY;
+
+ public DrawRoundRect(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mRadiusX = radiusX;
+ mRadiusY = radiusY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom, mRadiusX, mRadiusY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawRoundRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom
+ + " (" + mRadiusX + " " + mRadiusY + ");";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ float srcRadiusX = buffer.readFloat();
+ float srcRadiusY = buffer.readFloat();
+
+ DrawRoundRect op = new DrawRoundRect(sLeft, srcTop, srcRight,
+ srcBottom, srcRadiusX, srcRadiusY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_ROUND_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ buffer.start(Operations.DRAW_ROUND_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeFloat(radiusX);
+ buffer.writeFloat(radiusY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawRoundRect(mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mRadiusX,
+ mRadiusY
+ );
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
new file mode 100644
index 000000000000..1856e3097ec0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTextOnPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mPathId;
+ public int mTextId;
+ float mVOffset;
+ float mHOffset;
+
+ public DrawTextOnPath(int textId, int pathId, float hOffset, float vOffset) {
+ mPathId = pathId;
+ mTextId = textId;
+ mHOffset = vOffset;
+ mVOffset = hOffset;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTextId, mPathId, mHOffset, mVOffset);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawTextOnPath " + " " + mPathId + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int textId = buffer.readInt();
+ int pathId = buffer.readInt();
+ float hOffset = buffer.readFloat();
+ float vOffset = buffer.readFloat();
+ DrawTextOnPath op = new DrawTextOnPath(textId, pathId, hOffset, vOffset);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawTextOnPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_TEXT_ON_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
+ buffer.start(Operations.DRAW_TEXT_ON_PATH);
+ buffer.writeInt(textId);
+ buffer.writeInt(pathId);
+ buffer.writeFloat(hOffset);
+ buffer.writeFloat(vOffset);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTextOnPath(mTextId, mPathId, mHOffset, mVOffset);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
new file mode 100644
index 000000000000..a0992528d981
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTextRun extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mTextID;
+ int mStart = 0;
+ int mEnd = 0;
+ int mContextStart = 0;
+ int mContextEnd = 0;
+ float mX = 0f;
+ float mY = 0f;
+ boolean mRtl = false;
+
+ public DrawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ mTextID = textID;
+ mStart = start;
+ mEnd = end;
+ mContextStart = contextStart;
+ mContextEnd = contextEnd;
+ mX = x;
+ mY = y;
+ mRtl = rtl;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int text = buffer.readInt();
+ int start = buffer.readInt();
+ int end = buffer.readInt();
+ int contextStart = buffer.readInt();
+ int contextEnd = buffer.readInt();
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ boolean rtl = buffer.readBoolean();
+ DrawTextRun op = new DrawTextRun(text, start, end, contextStart, contextEnd, x, y, rtl);
+
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public int id() {
+ return 0;
+ }
+
+ public void apply(WireBuffer buffer,
+ int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ buffer.start(Operations.DRAW_TEXT_RUN);
+ buffer.writeInt(textID);
+ buffer.writeInt(start);
+ buffer.writeInt(end);
+ buffer.writeInt(contextStart);
+ buffer.writeInt(contextEnd);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeBoolean(rtl);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
new file mode 100644
index 000000000000..ef0a4ad2eff3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTweenPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mTween;
+ float mStart;
+ float mStop;
+ int mPath1Id;
+ int mPath2Id;
+
+ public DrawTweenPath(
+ int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ mTween = tween;
+ mStart = start;
+ mStop = stop;
+ mPath1Id = path1Id;
+ mPath2Id = path2Id;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mPath1Id,
+ mPath2Id,
+ mTween,
+ mStart,
+ mStop);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawTweenPath " + mPath1Id + " " + mPath2Id
+ + " " + mTween + " " + mStart + " "
+ + "- " + mStop + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int path1Id = buffer.readInt();
+ int path2Id = buffer.readInt();
+ float tween = buffer.readFloat();
+ float start = buffer.readFloat();
+ float stop = buffer.readFloat();
+ DrawTweenPath op = new DrawTweenPath(path1Id, path2Id,
+ tween, start, stop);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawTweenPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_TWEEN_PATH;
+ }
+
+ public void apply(WireBuffer buffer,
+ int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ buffer.start(Operations.DRAW_TWEEN_PATH);
+ buffer.writeInt(path1Id);
+ buffer.writeInt(path2Id);
+ buffer.writeFloat(tween);
+ buffer.writeFloat(start);
+ buffer.writeFloat(stop);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTweenPath(mPath1Id,
+ mPath2Id,
+ mTween,
+ mStart,
+ mStop);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index eca43c5e3281..aabed15e833d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -26,11 +26,11 @@ import java.util.List;
/**
* Describe some basic information for a RemoteCompose document
- *
+ * <p>
* It encodes the version of the document (following semantic versioning) as well
* as the dimensions of the document in pixels.
*/
-public class Header implements RemoteComposeOperation {
+public class Header implements RemoteComposeOperation {
public static final int MAJOR_VERSION = 0;
public static final int MINOR_VERSION = 1;
public static final int PATCH_VERSION = 0;
@@ -89,7 +89,8 @@ public class Header implements RemoteComposeOperation {
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
new file mode 100644
index 000000000000..482e0e22bd57
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixRestore extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+
+ public MatrixRestore() {
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixRestore;";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+
+ MatrixRestore op = new MatrixRestore();
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "MatrixRestore";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_RESTORE;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MATRIX_RESTORE);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixRestore();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
new file mode 100644
index 000000000000..d6c89e0d2c64
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixRotate extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mRotate, mPivotX, mPivotY;
+
+ public MatrixRotate(float rotate, float pivotX, float pivotY) {
+ mRotate = rotate;
+ mPivotX = pivotX;
+ mPivotY = pivotY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mRotate, mPivotX, mPivotY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mRotate + ", " + mPivotX + ", " + mPivotY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float rotate = buffer.readFloat();
+ float pivotX = buffer.readFloat();
+ float pivotY = buffer.readFloat();
+ MatrixRotate op = new MatrixRotate(rotate, pivotX, pivotY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_ROTATE;
+ }
+
+ public void apply(WireBuffer buffer, float rotate, float pivotX, float pivotY) {
+ buffer.start(Operations.MATRIX_ROTATE);
+ buffer.writeFloat(rotate);
+ buffer.writeFloat(pivotX);
+ buffer.writeFloat(pivotY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixRotate(mRotate, mPivotX, mPivotY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
new file mode 100644
index 000000000000..d3d5bfba8992
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixSave extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+
+ public MatrixSave() {
+
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixSave;";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+
+ MatrixSave op = new MatrixSave();
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SAVE;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MATRIX_SAVE);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixSave();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
new file mode 100644
index 000000000000..28aa68dd5884
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixScale extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mScaleX, mScaleY;
+ float mCenterX, mCenterY;
+
+ public MatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
+ mScaleX = scaleX;
+ mScaleY = scaleY;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mScaleX, mScaleY, mCenterX, mCenterY);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixScale " + mScaleY + ", " + mScaleY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float scaleX = buffer.readFloat();
+ float scaleY = buffer.readFloat();
+ float centerX = buffer.readFloat();
+ float centerY = buffer.readFloat();
+ MatrixScale op = new MatrixScale(scaleX, scaleY, centerX, centerY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SCALE;
+ }
+
+ public void apply(WireBuffer buffer, float scaleX, float scaleY,
+ float centerX, float centerY) {
+ buffer.start(Operations.MATRIX_SCALE);
+ buffer.writeFloat(scaleX);
+ buffer.writeFloat(scaleY);
+ buffer.writeFloat(centerX);
+ buffer.writeFloat(centerY);
+
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.mtrixScale(mScaleX, mScaleY, mCenterX, mCenterY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
new file mode 100644
index 000000000000..a3888997c758
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.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 com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixSkew extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mSkewX, mSkewY;
+
+ public MatrixSkew(float skewX, float skewY) {
+ mSkewX = skewX;
+ mSkewY = skewY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mSkewX, mSkewY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mSkewY + ", " + mSkewY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float skewX = buffer.readFloat();
+ float skewY = buffer.readFloat();
+ MatrixSkew op = new MatrixSkew(skewX, skewY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SKEW;
+ }
+
+ public void apply(WireBuffer buffer, float skewX, float skewY) {
+ buffer.start(Operations.MATRIX_SKEW);
+ buffer.writeFloat(skewX);
+ buffer.writeFloat(skewY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixSkew(mSkewX, mSkewY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
new file mode 100644
index 000000000000..32987521e041
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.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 com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixTranslate extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mTranslateX, mTranslateY;
+
+ public MatrixTranslate(float translateX, float translateY) {
+ mTranslateX = translateX;
+ mTranslateY = translateY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTranslateX, mTranslateY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mTranslateY + ", " + mTranslateY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float translateX = buffer.readFloat();
+ float translateY = buffer.readFloat();
+ MatrixTranslate op = new MatrixTranslate(translateX, translateY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_TRANSLATE;
+ }
+
+ public void apply(WireBuffer buffer, float translateX, float translateY) {
+ buffer.start(Operations.MATRIX_TRANSLATE);
+ buffer.writeFloat(translateX);
+ buffer.writeFloat(translateY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixTranslate(mTranslateX, mTranslateY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
new file mode 100644
index 000000000000..e5683ece7919
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
+import java.util.List;
+
+public class PaintData extends PaintOperation {
+ public PaintBundle mPaintData = new PaintBundle();
+ public static final Companion COMPANION = new Companion();
+ public static final int MAX_STRING_SIZE = 4000;
+
+ public PaintData() {
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mPaintData);
+ }
+
+ @Override
+ public String toString() {
+ return "PaintData " + "\"" + mPaintData + "\"";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public String name() {
+ return "TextData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.PAINT_VALUES;
+ }
+
+ public void apply(WireBuffer buffer, PaintBundle paintBundle) {
+ buffer.start(Operations.PAINT_VALUES);
+ paintBundle.writeBundle(buffer);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ PaintData data = new PaintData();
+ data.mPaintData.readBundle(buffer);
+ operations.add(data);
+ }
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.applyPaint(mPaintData);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
new file mode 100644
index 000000000000..2646b27b1f51
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class PathData implements Operation {
+ public static final Companion COMPANION = new Companion();
+ int mInstanceId;
+ float[] mRef;
+ float[] mFloatPath;
+ float[] mRetFloats;
+
+ PathData(int instanceId, float[] floatPath) {
+ mInstanceId = instanceId;
+ mFloatPath = floatPath;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mInstanceId, mFloatPath);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return pathString(mFloatPath);
+ }
+
+ public float[] getFloatPath(PaintContext context) {
+ float[] ret = mRetFloats; // Assume retFloats is declared elsewhere
+ if (ret == null) {
+ return mFloatPath; // Assume floatPath is declared elsewhere
+ }
+ float[] localRef = mRef; // Assume ref is of type Float[]
+ if (localRef == null) {
+ for (int i = 0; i < mFloatPath.length; i++) {
+ ret[i] = mFloatPath[i];
+ }
+ } else {
+ for (int i = 0; i < mFloatPath.length; i++) {
+ float lr = localRef[i];
+ if (Float.isNaN(lr)) {
+ ret[i] = Utils.getActualValue(lr);
+ } else {
+ ret[i] = mFloatPath[i];
+ }
+ }
+ }
+ return ret;
+ }
+
+ public static final int MOVE = 10;
+ public static final int LINE = 11;
+ public static final int QUADRATIC = 12;
+ public static final int CONIC = 13;
+ public static final int CUBIC = 14;
+ public static final int CLOSE = 15;
+ public static final int DONE = 16;
+ public static final float MOVE_NAN = Utils.asNan(MOVE);
+ public static final float LINE_NAN = Utils.asNan(LINE);
+ public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
+ public static final float CONIC_NAN = Utils.asNan(CONIC);
+ public static final float CUBIC_NAN = Utils.asNan(CUBIC);
+ public static final float CLOSE_NAN = Utils.asNan(CLOSE);
+ public static final float DONE_NAN = Utils.asNan(DONE);
+
+ public static class Companion implements CompanionOperation {
+
+ private Companion() {
+ }
+
+ @Override
+ public String name() {
+ return "BitmapData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DATA_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id, float[] data) {
+ buffer.start(Operations.DATA_PATH);
+ buffer.writeInt(id);
+ buffer.writeInt(data.length);
+ for (int i = 0; i < data.length; i++) {
+ buffer.writeFloat(data[i]);
+ }
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int imageId = buffer.readInt();
+ int len = buffer.readInt();
+ float[] data = new float[len];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = buffer.readFloat();
+ }
+ operations.add(new PathData(imageId, data));
+ }
+ }
+
+ public static String pathString(float[] path) {
+ if (path == null) {
+ return "null";
+ }
+ StringBuilder str = new StringBuilder();
+ for (int i = 0; i < path.length; i++) {
+ if (i != 0) {
+ str.append(" ");
+ }
+ if (Float.isNaN(path[i])) {
+ int id = Utils.idFromNan(path[i]); // Assume idFromNan is defined elsewhere
+ if (id <= DONE) { // Assume DONE is a constant
+ switch (id) {
+ case MOVE:
+ str.append("M");
+ break;
+ case LINE:
+ str.append("L");
+ break;
+ case QUADRATIC:
+ str.append("Q");
+ break;
+ case CONIC:
+ str.append("R");
+ break;
+ case CUBIC:
+ str.append("C");
+ break;
+ case CLOSE:
+ str.append("Z");
+ break;
+ case DONE:
+ str.append(".");
+ break;
+ default:
+ str.append("X");
+ break;
+ }
+ } else {
+ str.append("(" + id + ")");
+ }
+ } else {
+ str.append(path[i]);
+ }
+ }
+ return str.toString();
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.loadPathData(mInstanceId, mFloatPath);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index ad4caea7aef8..6d924eb70c50 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -28,7 +28,7 @@ import java.util.List;
/**
* Describe some basic information for a RemoteCompose document
- *
+ * <p>
* It encodes the version of the document (following semantic versioning) as well
* as the dimensions of the document in pixels.
*/
@@ -100,21 +100,21 @@ public class RootContentBehavior implements RemoteComposeOperation {
/**
* Sets the way the player handles the content
*
- * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
* @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
- * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
- * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
- * the LAYOUT modes are:
- * - LAYOUT_MATCH_PARENT
- * - LAYOUT_WRAP_CONTENT
- * or adding an horizontal mode and a vertical mode:
- * - LAYOUT_HORIZONTAL_MATCH_PARENT
- * - LAYOUT_HORIZONTAL_WRAP_CONTENT
- * - LAYOUT_HORIZONTAL_FIXED
- * - LAYOUT_VERTICAL_MATCH_PARENT
- * - LAYOUT_VERTICAL_WRAP_CONTENT
- * - LAYOUT_VERTICAL_FIXED
- * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
*/
public RootContentBehavior(int scroll, int alignment, int sizing, int mode) {
switch (scroll) {
@@ -149,10 +149,12 @@ public class RootContentBehavior implements RemoteComposeOperation {
switch (sizing) {
case SIZING_LAYOUT: {
Log.e(TAG, "sizing_layout is not yet supported");
- } break;
+ }
+ break;
case SIZING_SCALE: {
mSizing = sizing;
- } break;
+ }
+ break;
default: {
Log.e(TAG, "incorrect sizing value " + sizing);
}
@@ -200,7 +202,8 @@ public class RootContentBehavior implements RemoteComposeOperation {
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
new file mode 100644
index 000000000000..00e2f2058e89
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -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.internal.widget.remotecompose.core.operations;
+
+public class Utils {
+ public static float asNan(int v) {
+ return Float.intBitsToFloat(v | -0x800000);
+ }
+
+ public static int idFromNan(float value) {
+ int b = Float.floatToRawIntBits(value);
+ return b & 0xFFFFF;
+ }
+
+ public static float getActualValue(float lr) {
+ return 0;
+ }
+
+ String getFloatString(float value) {
+ if (Float.isNaN(value)) {
+ int id = idFromNan(value);
+ if (id > 0) {
+ return "NaN(" + id + ")";
+ }
+ }
+ return "" + value;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
new file mode 100644
index 000000000000..8abb0bfff338
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.Arrays;
+
+public class PaintBundle {
+ int[] mArray = new int[200];
+ int mPos = 0;
+
+ public void applyPaintChange(PaintChanges p) {
+ int i = 0;
+ int mask = 0;
+ while (i < mPos) {
+ int cmd = mArray[i++];
+ mask = mask | (1 << (cmd - 1));
+ switch (cmd & 0xFFFF) {
+ case TEXT_SIZE: {
+ p.setTextSize(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case TYPEFACE:
+ int style = (cmd >> 16);
+ int weight = style & 0x3ff;
+ boolean italic = (style >> 10) > 0;
+ int font_type = mArray[i++];
+
+ p.setTypeFace(font_type, weight, italic);
+ break;
+ case COLOR: {
+ p.setColor(mArray[i++]);
+ break;
+ }
+ case STROKE_WIDTH: {
+ p.setStrokeWidth(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case STROKE_MITER: {
+ p.setStrokeMiter(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case STROKE_CAP: {
+ p.setStrokeCap(cmd >> 16);
+ break;
+ }
+ case STYLE: {
+ p.setStyle(cmd >> 16);
+ break;
+ }
+ case SHADER: {
+ break;
+ }
+ case STROKE_JOIN: {
+ p.setStrokeJoin(cmd >> 16);
+ break;
+ }
+ case IMAGE_FILTER_QUALITY: {
+ p.setImageFilterQuality(cmd >> 16);
+ break;
+ }
+ case BLEND_MODE: {
+ p.setBlendMode(cmd >> 16);
+ break;
+ }
+ case FILTER_BITMAP: {
+ p.setFilterBitmap(!((cmd >> 16) == 0));
+ break;
+ }
+
+ case GRADIENT: {
+ i = callSetGradient(cmd, mArray, i, p);
+ break;
+ }
+ case COLOR_FILTER: {
+ p.setColorFilter(mArray[i++], cmd >> 16);
+ break;
+ }
+ case ALPHA: {
+ p.setAlpha(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ }
+ }
+
+ mask = (~mask) & PaintChanges.VALID_BITS;
+
+ p.clear(mask);
+ }
+
+ private String toName(int id) {
+ switch (id) {
+ case TEXT_SIZE:
+ return "TEXT_SIZE";
+
+ case COLOR:
+ return "COLOR";
+ case STROKE_WIDTH:
+ return "STROKE_WIDTH";
+ case STROKE_MITER:
+ return "STROKE_MITER";
+ case TYPEFACE:
+ return "TYPEFACE";
+ case STROKE_CAP:
+ return "CAP";
+ case STYLE:
+ return "STYLE";
+ case SHADER:
+ return "SHADER";
+ case IMAGE_FILTER_QUALITY:
+ return "IMAGE_FILTER_QUALITY";
+ case BLEND_MODE:
+ return "BLEND_MODE";
+ case FILTER_BITMAP:
+ return "FILTER_BITMAP";
+ case GRADIENT:
+ return "GRADIENT_LINEAR";
+ case ALPHA:
+ return "ALPHA";
+ case COLOR_FILTER:
+ return "COLOR_FILTER";
+
+ }
+ return "????" + id + "????";
+ }
+
+ private static String colorInt(int color) {
+ String str = "000000000000" + Integer.toHexString(color);
+ return "0x" + str.substring(str.length() - 8);
+ }
+
+ private static String colorInt(int[] color) {
+ String str = "[";
+ for (int i = 0; i < color.length; i++) {
+ if (i > 0) {
+ str += ", ";
+ }
+ str += colorInt(color[i]);
+ }
+ return str + "]";
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("\n");
+ int i = 0;
+ while (i < mPos) {
+ int cmd = mArray[i++];
+ int type = cmd & 0xFFFF;
+ switch (type) {
+
+ case TEXT_SIZE: {
+ ret.append(" TextSize(" + Float.intBitsToFloat(mArray[i++]));
+ }
+
+ break;
+ case TYPEFACE: {
+ int style = (cmd >> 16);
+ int weight = style & 0x3ff;
+ boolean italic = (style >> 10) > 0;
+ int font_type = mArray[i++];
+ ret.append(" TypeFace(" + (font_type + ", "
+ + weight + ", " + italic));
+ }
+ break;
+ case COLOR: {
+ ret.append(" Color(" + colorInt(mArray[i++]));
+ }
+ break;
+ case STROKE_WIDTH: {
+ ret.append(" StrokeWidth("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case STROKE_MITER: {
+ ret.append(" StrokeMiter("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case STROKE_CAP: {
+ ret.append(" StrokeCap("
+ + (cmd >> 16));
+ }
+ break;
+ case STYLE: {
+ ret.append(" Style(" + (cmd >> 16));
+ }
+ break;
+ case COLOR_FILTER: {
+ ret.append(" ColorFilter(color="
+ + colorInt(mArray[i++])
+ + ", mode=" + blendModeString(cmd >> 16));
+ }
+ break;
+ case SHADER: {
+ }
+ break;
+ case ALPHA: {
+ ret.append(" Alpha("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case IMAGE_FILTER_QUALITY: {
+ ret.append(" ImageFilterQuality(" + (cmd >> 16));
+ }
+ break;
+ case BLEND_MODE: {
+ ret.append(" BlendMode(" + blendModeString(cmd >> 16));
+ }
+ break;
+ case FILTER_BITMAP: {
+ ret.append(" FilterBitmap("
+ + (!((cmd >> 16) == 0)));
+ }
+ break;
+ case STROKE_JOIN: {
+ ret.append(" StrokeJoin(" + (cmd >> 16));
+ }
+ break;
+ case ANTI_ALIAS: {
+ ret.append(" AntiAlias(" + (cmd >> 16));
+ }
+ break;
+ case GRADIENT: {
+ i = callPrintGradient(cmd, mArray, i, ret);
+ }
+ }
+ ret.append("),\n");
+ }
+ return ret.toString();
+ }
+
+
+ int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) {
+ int ret = i;
+ int type = (cmd >> 16);
+ switch (type) {
+
+ case 0: {
+ p.append(" LinearGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" start = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ p.append(" end = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ int tileMode = array[ret++];
+ p.append(" tileMode = " + tileMode + "\n ");
+ }
+
+ break;
+ case 1: {
+ p.append(" RadialGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" center = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ p.append(" radius =");
+ p.append(" " + Float.intBitsToFloat(array[ret++]) + ",\n");
+ int tileMode = array[ret++];
+ p.append(" tileMode = " + tileMode + "\n ");
+ }
+
+ break;
+ case 2: {
+ p.append(" SweepGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" center = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n ");
+
+ }
+ break;
+ default: {
+ p.append("GRADIENT_??????!!!!");
+ }
+ }
+
+ return ret;
+ }
+
+ int callSetGradient(int cmd, int[] array, int i, PaintChanges p) {
+ int ret = i;
+ int gradientType = (cmd >> 16);
+
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < colors.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ if (colors == null) {
+ return ret;
+ }
+
+
+ switch (gradientType) {
+
+ case LINEAR_GRADIENT: {
+ float startX = Float.intBitsToFloat(array[ret++]);
+ float startY = Float.intBitsToFloat(array[ret++]);
+ float endX = Float.intBitsToFloat(array[ret++]);
+ float endY = Float.intBitsToFloat(array[ret++]);
+ int tileMode = array[ret++];
+ p.setLinearGradient(colors, stops, startX,
+ startY, endX, endY, tileMode);
+ }
+
+ break;
+ case RADIAL_GRADIENT: {
+ float centerX = Float.intBitsToFloat(array[ret++]);
+ float centerY = Float.intBitsToFloat(array[ret++]);
+ float radius = Float.intBitsToFloat(array[ret++]);
+ int tileMode = array[ret++];
+ p.setRadialGradient(colors, stops, centerX, centerY,
+ radius, tileMode);
+ }
+ break;
+ case SWEEP_GRADIENT: {
+ float centerX = Float.intBitsToFloat(array[ret++]);
+ float centerY = Float.intBitsToFloat(array[ret++]);
+ p.setSweepGradient(colors, stops, centerX, centerY);
+ }
+ }
+
+ return ret;
+ }
+
+ public void writeBundle(WireBuffer buffer) {
+ buffer.writeInt(mPos);
+ for (int index = 0; index < mPos; index++) {
+ buffer.writeInt(mArray[index]);
+ }
+ }
+
+ public void readBundle(WireBuffer buffer) {
+ int len = buffer.readInt();
+ if (len <= 0 || len > 1024) {
+ throw new RuntimeException("buffer corrupt paint len = " + len);
+ }
+ mArray = new int[len];
+ for (int i = 0; i < mArray.length; i++) {
+ mArray[i] = buffer.readInt();
+ }
+ mPos = len;
+ }
+
+ public static final int TEXT_SIZE = 1; // float
+
+ public static final int COLOR = 4; // int
+ public static final int STROKE_WIDTH = 5; // float
+ public static final int STROKE_MITER = 6;
+ public static final int STROKE_CAP = 7; // int
+ public static final int STYLE = 8; // int
+ public static final int SHADER = 9; // int
+ public static final int IMAGE_FILTER_QUALITY = 10; // int
+ public static final int GRADIENT = 11;
+ public static final int ALPHA = 12;
+ public static final int COLOR_FILTER = 13;
+ public static final int ANTI_ALIAS = 14;
+ public static final int STROKE_JOIN = 15;
+ public static final int TYPEFACE = 16;
+ public static final int FILTER_BITMAP = 17;
+ public static final int BLEND_MODE = 18;
+
+
+ public static final int BLEND_MODE_CLEAR = 0;
+ public static final int BLEND_MODE_SRC = 1;
+ public static final int BLEND_MODE_DST = 2;
+ public static final int BLEND_MODE_SRC_OVER = 3;
+ public static final int BLEND_MODE_DST_OVER = 4;
+ public static final int BLEND_MODE_SRC_IN = 5;
+ public static final int BLEND_MODE_DST_IN = 6;
+ public static final int BLEND_MODE_SRC_OUT = 7;
+ public static final int BLEND_MODE_DST_OUT = 8;
+ public static final int BLEND_MODE_SRC_ATOP = 9;
+ public static final int BLEND_MODE_DST_ATOP = 10;
+ public static final int BLEND_MODE_XOR = 11;
+ public static final int BLEND_MODE_PLUS = 12;
+ public static final int BLEND_MODE_MODULATE = 13;
+ public static final int BLEND_MODE_SCREEN = 14;
+ public static final int BLEND_MODE_OVERLAY = 15;
+ public static final int BLEND_MODE_DARKEN = 16;
+ public static final int BLEND_MODE_LIGHTEN = 17;
+ public static final int BLEND_MODE_COLOR_DODGE = 18;
+ public static final int BLEND_MODE_COLOR_BURN = 19;
+ public static final int BLEND_MODE_HARD_LIGHT = 20;
+ public static final int BLEND_MODE_SOFT_LIGHT = 21;
+ public static final int BLEND_MODE_DIFFERENCE = 22;
+ public static final int BLEND_MODE_EXCLUSION = 23;
+ public static final int BLEND_MODE_MULTIPLY = 24;
+ public static final int BLEND_MODE_HUE = 25;
+ public static final int BLEND_MODE_SATURATION = 26;
+ public static final int BLEND_MODE_COLOR = 27;
+ public static final int BLEND_MODE_LUMINOSITY = 28;
+ public static final int BLEND_MODE_NULL = 29;
+ public static final int PORTER_MODE_ADD = 30;
+
+ public static final int FONT_NORMAL = 0;
+ public static final int FONT_BOLD = 1;
+ public static final int FONT_ITALIC = 2;
+ public static final int FONT_BOLD_ITALIC = 3;
+
+ public static final int FONT_TYPE_DEFAULT = 0;
+ public static final int FONT_TYPE_SANS_SERIF = 1;
+ public static final int FONT_TYPE_SERIF = 2;
+ public static final int FONT_TYPE_MONOSPACE = 3;
+
+ public static final int STYLE_FILL = 0;
+ public static final int STYLE_STROKE = 1;
+ public static final int STYLE_FILL_AND_STROKE = 2;
+ public static final int LINEAR_GRADIENT = 0;
+ public static final int RADIAL_GRADIENT = 1;
+ public static final int SWEEP_GRADIENT = 2;
+
+ /**
+ * sets a shader that draws a linear gradient along a line.
+ *
+ * @param startX The x-coordinate for the start of the gradient line
+ * @param startY The y-coordinate for the start of the gradient line
+ * @param endX The x-coordinate for the end of the gradient line
+ * @param endY The y-coordinate for the end of the gradient line
+ * @param colors The sRGB colors to be distributed along the gradient line
+ * @param stops May be null. The relative positions [0..1] of
+ * each corresponding color in the colors array. If this is null,
+ * the colors are distributed evenly along the gradient line.
+ * @param tileMode The Shader tiling mode
+ */
+ public void setLinearGradient(int[] colors,
+ float[] stops,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (LINEAR_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(startX);
+ mArray[mPos++] = Float.floatToRawIntBits(startY);
+ mArray[mPos++] = Float.floatToRawIntBits(endX);
+ mArray[mPos++] = Float.floatToRawIntBits(endY);
+ mArray[mPos++] = tileMode;
+ }
+
+ /**
+ * Set a shader that draws a sweep gradient around a center point.
+ *
+ * @param centerX The x-coordinate of the center
+ * @param centerY The y-coordinate of the center
+ * @param colors The sRGB colors to be distributed around the center.
+ * There must be at least 2 colors in the array.
+ * @param stops May be NULL. The relative position of
+ * each corresponding color in the colors array, beginning
+ * with 0 and ending with 1.0. If the values are not
+ * monotonic, the drawing may produce unexpected results.
+ * If positions is NULL, then the colors are automatically
+ * spaced evenly.
+ */
+ public void setSweepGradient(int[] colors, float[] stops, float centerX, float centerY) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (SWEEP_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(centerX);
+ mArray[mPos++] = Float.floatToRawIntBits(centerY);
+ }
+
+ /**
+ * Sets a shader that draws a radial gradient given the center and radius.
+ *
+ * @param centerX The x-coordinate of the center of the radius
+ * @param centerY The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the gradient.
+ * @param colors The sRGB colors distributed between the center and edge
+ * @param stops May be <code>null</code>.
+ * Valid values are between <code>0.0f</code> and
+ * <code>1.0f</code>. The relative position of each
+ * corresponding color in
+ * the colors array. If <code>null</code>, colors are
+ * distributed evenly
+ * between the center and edge of the circle.
+ * @param tileMode The Shader tiling mode
+ */
+ public void setRadialGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (RADIAL_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(centerX);
+ mArray[mPos++] = Float.floatToRawIntBits(centerY);
+ mArray[mPos++] = Float.floatToRawIntBits(radius);
+ mArray[mPos++] = tileMode;
+
+ }
+
+ /**
+ * Create a color filter that uses the specified color and Porter-Duff mode.
+ *
+ * @param color The ARGB source color used with the Porter-Duff mode
+ * @param mode The porter-duff mode that is applied
+ */
+ public void setColorFilter(int color, int mode) {
+ mArray[mPos] = COLOR_FILTER | (mode << 16);
+ mPos++;
+ mArray[mPos++] = color;
+ }
+
+ /**
+ * Set the paint's text size. This value must be > 0
+ *
+ * @param size set the paint's text size in pixel units.
+ */
+ public void setTextSize(float size) {
+ int p = mPos;
+ mArray[mPos] = TEXT_SIZE;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(size);
+ mPos++;
+ }
+
+ /**
+ * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace
+ * @param weight 100-1000
+ * @param italic tur
+ */
+ public void setTextStyle(int fontType, int weight, boolean italic) {
+ int style = (weight & 0x3FF) | (italic ? 2048 : 0); // pack the weight and italic
+ mArray[mPos++] = TYPEFACE | (style << 16);
+ mArray[mPos++] = fontType;
+ }
+
+ /**
+ * Set the width for stroking.
+ * Pass 0 to stroke in hairline mode.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
+ *
+ * @param width set the paint's stroke width, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeWidth(float width) {
+ mArray[mPos] = STROKE_WIDTH;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(width);
+ mPos++;
+ }
+
+ public void setColor(int color) {
+ mArray[mPos] = COLOR;
+ mPos++;
+ mArray[mPos] = color;
+ mPos++;
+ }
+
+ /**
+ * Set the paint's Cap.
+ *
+ * @param cap set the paint's line cap style, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeCap(int cap) {
+ mArray[mPos] = STROKE_CAP | (cap << 16);
+ mPos++;
+ }
+
+ public void setStyle(int style) {
+ mArray[mPos] = STYLE | (style << 16);
+ mPos++;
+ }
+
+ public void setShader(int shader, String shaderString) {
+ mArray[mPos] = SHADER | (shader << 16);
+ mPos++;
+ }
+
+ public void setAlpha(float alpha) {
+ mArray[mPos] = ALPHA;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(alpha);
+ mPos++;
+ }
+
+ /**
+ * Set the paint's stroke miter value. This is used to control the behavior
+ * of miter joins when the joins angle is sharp. This value must be >= 0.
+ *
+ * @param miter set the miter limit on the paint, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeMiter(float miter) {
+ mArray[mPos] = STROKE_MITER;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(miter);
+ mPos++;
+ }
+
+ /**
+ * Set the paint's Join.
+ *
+ * @param join set the paint's Join, used whenever the paint's style is
+ * Stroke or StrokeAndFill.
+ */
+ public void setStrokeJoin(int join) {
+ mArray[mPos] = STROKE_JOIN | (join << 16);
+ mPos++;
+ }
+
+ public void setFilterBitmap(boolean filter) {
+ mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0);
+ mPos++;
+ }
+
+ /**
+ * Set or clear the blend mode. A blend mode defines how source pixels
+ * (generated by a drawing command) are composited with the
+ * destination pixels
+ * (content of the render target).
+ *
+ *
+ * @param blendmode The blend mode to be installed in the paint
+ */
+ public void setBlendMode(int blendmode) {
+ mArray[mPos] = BLEND_MODE | (blendmode << 16);
+ mPos++;
+ }
+
+ /**
+ * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit
+ * AntiAliasing smooths out the edges of what is being drawn, but is has
+ * no impact on the interior of the shape. See setDither() and
+ * setFilterBitmap() to affect how colors are treated.
+ *
+ * @param aa true to set the antialias bit in the flags, false to clear it
+ */
+ public void setAntiAlias(boolean aa) {
+ mArray[mPos] = ANTI_ALIAS | (((aa) ? 1 : 0) << 16);
+ mPos++;
+ }
+
+ public void clear(long mask) { // unused for now
+ }
+
+ public void reset() {
+ mPos = 0;
+ }
+
+ public static String blendModeString(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return "CLEAR";
+ case PaintBundle.BLEND_MODE_SRC:
+ return "SRC";
+ case PaintBundle.BLEND_MODE_DST:
+ return "DST";
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return "SRC_OVER";
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return "DST_OVER";
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return "SRC_IN";
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return "DST_IN";
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return "SRC_OUT";
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return "DST_OUT";
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return "SRC_ATOP";
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return "DST_ATOP";
+ case PaintBundle.BLEND_MODE_XOR:
+ return "XOR";
+ case PaintBundle.BLEND_MODE_PLUS:
+ return "PLUS";
+ case PaintBundle.BLEND_MODE_MODULATE:
+ return "MODULATE";
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return "SCREEN";
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return "OVERLAY";
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return "DARKEN";
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return "LIGHTEN";
+ case PaintBundle.BLEND_MODE_COLOR_DODGE:
+ return "COLOR_DODGE";
+ case PaintBundle.BLEND_MODE_COLOR_BURN:
+ return "COLOR_BURN";
+ case PaintBundle.BLEND_MODE_HARD_LIGHT:
+ return "HARD_LIGHT";
+ case PaintBundle.BLEND_MODE_SOFT_LIGHT:
+ return "SOFT_LIGHT";
+ case PaintBundle.BLEND_MODE_DIFFERENCE:
+ return "DIFFERENCE";
+ case PaintBundle.BLEND_MODE_EXCLUSION:
+ return "EXCLUSION";
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return "MULTIPLY";
+ case PaintBundle.BLEND_MODE_HUE:
+ return "HUE";
+ case PaintBundle.BLEND_MODE_SATURATION:
+ return "SATURATION";
+ case PaintBundle.BLEND_MODE_COLOR:
+ return "COLOR";
+ case PaintBundle.BLEND_MODE_LUMINOSITY:
+ return "LUMINOSITY";
+ case PaintBundle.BLEND_MODE_NULL:
+ return "null";
+ case PaintBundle.PORTER_MODE_ADD:
+ return "ADD";
+ }
+ return "null";
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
new file mode 100644
index 000000000000..994bf6d7e327
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+public class PaintChangeAdapter implements PaintChanges {
+
+ @Override
+ public void setTextSize(float size) {
+
+ }
+
+ @Override
+ public void setTypeFace(int fontType, int weight, boolean italic) {
+
+ }
+
+
+ @Override
+ public void setStrokeWidth(float width) {
+
+ }
+
+ @Override
+ public void setColor(int color) {
+
+ }
+
+ @Override
+ public void setStrokeCap(int cap) {
+
+ }
+
+ @Override
+ public void setStyle(int style) {
+
+ }
+
+ @Override
+ public void setShader(int shader, String shaderString) {
+
+ }
+
+ @Override
+ public void setImageFilterQuality(int quality) {
+
+ }
+
+ @Override
+ public void setAlpha(float a) {
+
+ }
+
+ @Override
+ public void setStrokeMiter(float miter) {
+
+ }
+
+ @Override
+ public void setStrokeJoin(int join) {
+
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+
+ }
+
+ @Override
+ public void setBlendMode(int blendmode) {
+
+ }
+
+ @Override
+ public void setAntiAlias(boolean aa) {
+
+ }
+
+ @Override
+ public void clear(long mask) {
+
+ }
+
+ @Override
+ public void setLinearGradient(int[] colorsArray,
+ float[] stopsArray,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+
+ }
+
+ @Override
+ public void setRadialGradient(int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+
+ }
+
+ @Override
+ public void setSweepGradient(int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY) {
+
+ }
+
+ @Override
+ public void setColorFilter(int color, int mode) {
+
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
new file mode 100644
index 000000000000..87e58ac35930
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+public interface PaintChanges {
+
+
+ int CLEAR_TEXT_STYLE = 1 << (PaintBundle.TYPEFACE - 1);
+ int CLEAR_COLOR = 1 << (PaintBundle.COLOR - 1);
+ int CLEAR_STROKE_WIDTH = 1 << (PaintBundle.STROKE_WIDTH - 1);
+ int CLEAR_STROKE_MITER = 1 << (PaintBundle.STROKE_MITER - 1);
+ int CLEAR_CAP = 1 << (PaintBundle.STROKE_CAP - 1);
+ int CLEAR_STYLE = 1 << (PaintBundle.STYLE - 1);
+ int CLEAR_SHADER = 1 << (PaintBundle.SHADER - 1);
+ int CLEAR_IMAGE_FILTER_QUALITY =
+ 1 << (PaintBundle.IMAGE_FILTER_QUALITY - 1);
+ int CLEAR_RADIENT = 1 << (PaintBundle.GRADIENT - 1);
+ int CLEAR_ALPHA = 1 << (PaintBundle.ALPHA - 1);
+ int CLEAR_COLOR_FILTER = 1 << (PaintBundle.COLOR_FILTER - 1);
+ int VALID_BITS = 0x1FFF; // only the first 13 bit are valid now
+
+
+ void setTextSize(float size);
+ void setStrokeWidth(float width);
+ void setColor(int color);
+ void setStrokeCap(int cap);
+ void setStyle(int style);
+ void setShader(int shader, String shaderString);
+ void setImageFilterQuality(int quality);
+ void setAlpha(float a);
+ void setStrokeMiter(float miter);
+ void setStrokeJoin(int join);
+ void setFilterBitmap(boolean filter);
+ void setBlendMode(int mode);
+ void setAntiAlias(boolean aa);
+ void clear(long mask);
+ void setLinearGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode
+ );
+
+ void setRadialGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode
+ );
+
+ void setSweepGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY
+ );
+
+
+ void setColorFilter(int color, int mode);
+
+ void setTypeFace(int fontType, int weight, boolean italic);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
new file mode 100644
index 000000000000..1c0bec76bb62
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.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 com.android.internal.widget.remotecompose.core.operations.paint;
+
+public interface TextPaint {
+ void setARGB(int a, int r, int g, int b);
+
+ void setDither(boolean dither);
+
+ void setElegantTextHeight(boolean elegant);
+
+ void setEndHyphenEdit(int endHyphen);
+
+ void setFakeBoldText(boolean fakeBoldText);
+
+ void setFlags(int flags);
+
+ void setFontFeatureSettings(String settings);
+
+ void setHinting(int mode);
+
+ void setLetterSpacing(float letterSpacing);
+
+ void setLinearText(boolean linearText);
+
+ void setShadowLayer(float radius, float dx, float dy, int shadowColor);
+
+ void setStartHyphenEdit(int startHyphen);
+
+ void setStrikeThruText(boolean strikeThruText);
+
+ void setStrokeCap(int cap);
+
+ void setSubpixelText(boolean subpixelText);
+
+ void setTextAlign(int align);
+
+ void setTextLocale(int locale);
+
+ void setTextLocales(int localesArray);
+
+ void setTextScaleX(float scaleX);
+
+ void setTextSize(float textSize);
+
+ void setTextSkewX(float skewX);
+
+ void setUnderlineText(boolean underlineText);
+
+ void setWordSpacing(float wordSpacing);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index 3799cf6baac9..d0d6e6982a16 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -16,12 +16,25 @@
package com.android.internal.widget.remotecompose.player.platform;
import android.graphics.Bitmap;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.LinearGradient;
import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.RadialGradient;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.graphics.Typeface;
import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges;
/**
* An implementation of PaintContext for the Android Canvas.
@@ -71,7 +84,8 @@ public class AndroidPaintContext extends PaintContext {
int cdId) {
AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
if (androidContext.mRemoteComposeState.containsId(imageId)) {
- Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId);
+ Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState
+ .getFromId(imageId);
mCanvas.drawBitmap(
bitmap,
new Rect(srcLeft, srcTop, srcRight, srcBottom),
@@ -89,5 +103,501 @@ public class AndroidPaintContext extends PaintContext {
public void translate(float translateX, float translateY) {
mCanvas.translate(translateX, translateY);
}
+
+ @Override
+ public void drawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ mCanvas.drawArc(left, top, right, bottom, startAngle,
+ sweepAngle, true, mPaint);
+ }
+
+ @Override
+ public void drawBitmap(int id,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ if (androidContext.mRemoteComposeState.containsId(id)) {
+ Bitmap bitmap =
+ (Bitmap) androidContext.mRemoteComposeState.getFromId(id);
+ Rect src = new Rect(0, 0,
+ bitmap.getWidth(), bitmap.getHeight());
+ RectF dst = new RectF(left, top, right, bottom);
+ mCanvas.drawBitmap(bitmap, src, dst, mPaint);
+ }
+ }
+
+ @Override
+ public void drawCircle(float centerX, float centerY, float radius) {
+ mCanvas.drawCircle(centerX, centerY, radius, mPaint);
+ }
+
+ @Override
+ public void drawLine(float x1, float y1, float x2, float y2) {
+ mCanvas.drawLine(x1, y1, x2, y2, mPaint);
+ }
+
+ @Override
+ public void drawOval(float left, float top, float right, float bottom) {
+ mCanvas.drawOval(left, top, right, bottom, mPaint);
+ }
+
+ @Override
+ public void drawPath(int id, float start, float end) {
+ mCanvas.drawPath(getPath(id, start, end), mPaint);
+ }
+
+ @Override
+ public void drawRect(float left, float top, float right, float bottom) {
+ mCanvas.drawRect(left, top, right, bottom, mPaint);
+ }
+
+ @Override
+ public void drawRoundRect(float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ mCanvas.drawRoundRect(left, top, right, bottom,
+ radiusX, radiusY, mPaint);
+ }
+
+ @Override
+ public void drawTextOnPath(int textId,
+ int pathId,
+ float hOffset,
+ float vOffset) {
+ mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint);
+ }
+
+ @Override
+ public void drawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ String textToPaint = getText(textID).substring(start, end);
+ mCanvas.drawText(textToPaint, x, y, mPaint);
+ }
+
+ @Override
+ public void drawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float end) {
+ mCanvas.drawPath(getPath(path1Id, path2Id, tween, start, end), mPaint);
+ }
+
+ private static PorterDuff.Mode origamiToPorterDuffMode(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return PorterDuff.Mode.CLEAR;
+ case PaintBundle.BLEND_MODE_SRC:
+ return PorterDuff.Mode.SRC;
+ case PaintBundle.BLEND_MODE_DST:
+ return PorterDuff.Mode.DST;
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return PorterDuff.Mode.SRC_OVER;
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return PorterDuff.Mode.DST_OVER;
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return PorterDuff.Mode.SRC_IN;
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return PorterDuff.Mode.DST_IN;
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return PorterDuff.Mode.SRC_OUT;
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return PorterDuff.Mode.DST_OUT;
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return PorterDuff.Mode.SRC_ATOP;
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return PorterDuff.Mode.DST_ATOP;
+ case PaintBundle.BLEND_MODE_XOR:
+ return PorterDuff.Mode.XOR;
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return PorterDuff.Mode.SCREEN;
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return PorterDuff.Mode.OVERLAY;
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return PorterDuff.Mode.DARKEN;
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return PorterDuff.Mode.LIGHTEN;
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return PorterDuff.Mode.MULTIPLY;
+ case PaintBundle.PORTER_MODE_ADD:
+ return PorterDuff.Mode.ADD;
+ }
+ return PorterDuff.Mode.SRC_OVER;
+ }
+
+ public static BlendMode origamiToBlendMode(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return BlendMode.CLEAR;
+ case PaintBundle.BLEND_MODE_SRC:
+ return BlendMode.SRC;
+ case PaintBundle.BLEND_MODE_DST:
+ return BlendMode.DST;
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return BlendMode.SRC_OVER;
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return BlendMode.DST_OVER;
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return BlendMode.SRC_IN;
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return BlendMode.DST_IN;
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return BlendMode.SRC_OUT;
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return BlendMode.DST_OUT;
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return BlendMode.SRC_ATOP;
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return BlendMode.DST_ATOP;
+ case PaintBundle.BLEND_MODE_XOR:
+ return BlendMode.XOR;
+ case PaintBundle.BLEND_MODE_PLUS:
+ return BlendMode.PLUS;
+ case PaintBundle.BLEND_MODE_MODULATE:
+ return BlendMode.MODULATE;
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return BlendMode.SCREEN;
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return BlendMode.OVERLAY;
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return BlendMode.DARKEN;
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return BlendMode.LIGHTEN;
+ case PaintBundle.BLEND_MODE_COLOR_DODGE:
+ return BlendMode.COLOR_DODGE;
+ case PaintBundle.BLEND_MODE_COLOR_BURN:
+ return BlendMode.COLOR_BURN;
+ case PaintBundle.BLEND_MODE_HARD_LIGHT:
+ return BlendMode.HARD_LIGHT;
+ case PaintBundle.BLEND_MODE_SOFT_LIGHT:
+ return BlendMode.SOFT_LIGHT;
+ case PaintBundle.BLEND_MODE_DIFFERENCE:
+ return BlendMode.DIFFERENCE;
+ case PaintBundle.BLEND_MODE_EXCLUSION:
+ return BlendMode.EXCLUSION;
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return BlendMode.MULTIPLY;
+ case PaintBundle.BLEND_MODE_HUE:
+ return BlendMode.HUE;
+ case PaintBundle.BLEND_MODE_SATURATION:
+ return BlendMode.SATURATION;
+ case PaintBundle.BLEND_MODE_COLOR:
+ return BlendMode.COLOR;
+ case PaintBundle.BLEND_MODE_LUMINOSITY:
+ return BlendMode.LUMINOSITY;
+ case PaintBundle.BLEND_MODE_NULL:
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public void applyPaint(PaintBundle mPaintData) {
+ mPaintData.applyPaintChange(new PaintChanges() {
+ @Override
+ public void setTextSize(float size) {
+ mPaint.setTextSize(size);
+ }
+
+ @Override
+ public void setTypeFace(int fontType, int weight, boolean italic) {
+ int[] type = new int[]{Typeface.NORMAL, Typeface.BOLD,
+ Typeface.ITALIC, Typeface.BOLD_ITALIC};
+
+ switch (fontType) {
+ case PaintBundle.FONT_TYPE_DEFAULT: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.DEFAULT);
+ } else {
+ mPaint.setTypeface(Typeface.create(Typeface.DEFAULT,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_SERIF: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.SERIF);
+ } else {
+ mPaint.setTypeface(Typeface.create(Typeface.SERIF,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_SANS_SERIF: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.SANS_SERIF);
+ } else {
+ mPaint.setTypeface(
+ Typeface.create(Typeface.SANS_SERIF,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_MONOSPACE: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.MONOSPACE);
+ } else {
+ mPaint.setTypeface(
+ Typeface.create(Typeface.MONOSPACE,
+ weight, italic));
+ }
+
+ break;
+ }
+ }
+
+
+ }
+
+
+ @Override
+ public void setStrokeWidth(float width) {
+ mPaint.setStrokeWidth(width);
+ }
+
+ @Override
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ }
+
+ @Override
+ public void setStrokeCap(int cap) {
+ mPaint.setStrokeCap(Paint.Cap.values()[cap]);
+ }
+
+ @Override
+ public void setStyle(int style) {
+ mPaint.setStyle(Paint.Style.values()[style]);
+ }
+
+ @Override
+ public void setShader(int shader, String shaderString) {
+
+ }
+
+ @Override
+ public void setImageFilterQuality(int quality) {
+ System.out.println(">>>>>>>>>>>> ");
+ }
+
+ @Override
+ public void setBlendMode(int mode) {
+ mPaint.setBlendMode(origamiToBlendMode(mode));
+ }
+
+ @Override
+ public void setAlpha(float a) {
+ mPaint.setAlpha((int) (255 * a));
+ }
+
+ @Override
+ public void setStrokeMiter(float miter) {
+ mPaint.setStrokeMiter(miter);
+ }
+
+ @Override
+ public void setStrokeJoin(int join) {
+ mPaint.setStrokeJoin(Paint.Join.values()[join]);
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ mPaint.setFilterBitmap(filter);
+ }
+
+
+ @Override
+ public void setAntiAlias(boolean aa) {
+ mPaint.setAntiAlias(aa);
+ }
+
+ @Override
+ public void clear(long mask) {
+ if (true) return;
+ long m = mask;
+ int k = 1;
+ while (m > 0) {
+ if ((m & 1) == 1L) {
+ switch (k) {
+
+ case PaintBundle.COLOR_FILTER:
+ mPaint.setColorFilter(null);
+ System.out.println(">>>>>>>>>>>>> CLEAR!!!!");
+ break;
+ }
+ }
+ k++;
+ m = m >> 1;
+ }
+ }
+
+ Shader.TileMode[] mTilesModes = new Shader.TileMode[]{
+ Shader.TileMode.CLAMP,
+ Shader.TileMode.REPEAT,
+ Shader.TileMode.MIRROR};
+
+
+ @Override
+ public void setLinearGradient(int[] colors,
+ float[] stops,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+ mPaint.setShader(new LinearGradient(startX,
+ startY,
+ endX,
+ endY, colors, stops, mTilesModes[tileMode]));
+
+ }
+
+ @Override
+ public void setRadialGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+ mPaint.setShader(new RadialGradient(centerX, centerY, radius,
+ colors, stops, mTilesModes[tileMode]));
+ }
+
+ @Override
+ public void setSweepGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY) {
+ mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops));
+
+ }
+
+ @Override
+ public void setColorFilter(int color, int mode) {
+ PorterDuff.Mode pmode = origamiToPorterDuffMode(mode);
+ System.out.println("setting color filter to " + pmode.name());
+ if (pmode != null) {
+ mPaint.setColorFilter(
+ new PorterDuffColorFilter(color, pmode));
+ }
+ }
+ });
+ }
+
+ @Override
+ public void mtrixScale(float scaleX,
+ float scaleY,
+ float centerX,
+ float centerY) {
+ if (Float.isNaN(centerX)) {
+ mCanvas.scale(scaleX, scaleY);
+ } else {
+ mCanvas.scale(scaleX, scaleY, centerX, centerY);
+ }
+ }
+
+ @Override
+ public void matrixTranslate(float translateX, float translateY) {
+ mCanvas.translate(translateX, translateY);
+ }
+
+ @Override
+ public void matrixSkew(float skewX, float skewY) {
+ mCanvas.skew(skewX, skewY);
+ }
+
+ @Override
+ public void matrixRotate(float rotate, float pivotX, float pivotY) {
+ if (Float.isNaN(pivotX)) {
+ mCanvas.rotate(rotate);
+ } else {
+ mCanvas.rotate(rotate, pivotX, pivotY);
+
+ }
+ }
+
+ @Override
+ public void matrixSave() {
+ mCanvas.save();
+ }
+
+ @Override
+ public void matrixRestore() {
+ mCanvas.restore();
+ }
+
+ @Override
+ public void clipRect(float left, float top, float right, float bottom) {
+ mCanvas.clipRect(left, top, right, bottom);
+ }
+
+ @Override
+ public void clipPath(int pathId, int regionOp) {
+ Path path = getPath(pathId, 0, 1);
+ if (regionOp == ClipPath.DIFFERENCE) {
+ mCanvas.clipOutPath(path); // DIFFERENCE
+ } else {
+ mCanvas.clipPath(path); // INTERSECT
+ }
+ }
+
+ private Path getPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float end) {
+ if (tween == 0.0f) {
+ return getPath(path1Id, start, end);
+ }
+ if (tween == 1.0f) {
+ return getPath(path2Id, start, end);
+ }
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ float[] data1 =
+ (float[]) androidContext.mRemoteComposeState.getFromId(path1Id);
+ float[] data2 =
+ (float[]) androidContext.mRemoteComposeState.getFromId(path2Id);
+ float[] tmp = new float[data2.length];
+ for (int i = 0; i < tmp.length; i++) {
+ if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) {
+ tmp[i] = data1[i];
+ } else {
+ tmp[i] = (data2[i] - data1[i]) * tween + data1[i];
+ }
+ }
+ Path path = new Path();
+ FloatsToPath.genPath(path, tmp, start, end);
+ return path;
+ }
+
+ private Path getPath(int id, float start, float end) {
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ Path path = new Path();
+ if (androidContext.mRemoteComposeState.containsId(id)) {
+ float[] data =
+ (float[]) androidContext.mRemoteComposeState.getFromId(id);
+ FloatsToPath.genPath(path, data, start, end);
+ }
+ return path;
+ }
+
+ private String getText(int id) {
+ return (String) mContext.mRemoteComposeState.getFromId(id);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index ce15855fecfc..270e96f11942 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -43,6 +43,13 @@ class AndroidRemoteContext extends RemoteContext {
// Data handling
///////////////////////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void loadPathData(int instanceId, float[] floatPath) {
+ if (!mRemoteComposeState.containsId(instanceId)) {
+ mRemoteComposeState.cache(instanceId, floatPath);
+ }
+ }
+
/**
* Decode a byte array into an image and cache it using the given imageId
*
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
new file mode 100644
index 000000000000..2d766f8da295
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.idFromNan;
+
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.os.Build;
+
+import com.android.internal.widget.remotecompose.core.operations.PathData;
+
+public class FloatsToPath {
+ public static void genPath(Path retPath,
+ float[] floatPath,
+ float start,
+ float stop) {
+ int i = 0;
+ Path path = new Path(); // todo this should be cached for performance
+ while (i < floatPath.length) {
+ switch (idFromNan(floatPath[i])) {
+ case PathData.MOVE: {
+ i++;
+ path.moveTo(floatPath[i + 0], floatPath[i + 1]);
+ i += 2;
+ }
+ break;
+ case PathData.LINE: {
+ i += 3;
+ path.lineTo(floatPath[i + 0], floatPath[i + 1]);
+ i += 2;
+ }
+ break;
+ case PathData.QUADRATIC: {
+ i += 3;
+ path.quadTo(
+ floatPath[i + 0],
+ floatPath[i + 1],
+ floatPath[i + 2],
+ floatPath[i + 3]
+ );
+ i += 4;
+
+ }
+ break;
+ case PathData.CONIC: {
+ i += 3;
+ if (Build.VERSION.SDK_INT >= 34) {
+ path.conicTo(
+ floatPath[i + 0], floatPath[i + 1],
+ floatPath[i + 2], floatPath[i + 3],
+ floatPath[i + 4]
+ );
+ }
+ i += 5;
+ }
+ break;
+ case PathData.CUBIC: {
+ i += 3;
+ path.cubicTo(
+ floatPath[i + 0], floatPath[i + 1],
+ floatPath[i + 2], floatPath[i + 3],
+ floatPath[i + 4], floatPath[i + 5]
+ );
+ i += 6;
+ }
+ break;
+ case PathData.CLOSE: {
+
+ path.close();
+ i++;
+ }
+ break;
+ case PathData.DONE: {
+ i++;
+ }
+ break;
+ default: {
+ System.err.println(" Odd command "
+ + idFromNan(floatPath[i]));
+ }
+ }
+ }
+
+ retPath.reset();
+ if (start > 0f || stop < 1f) {
+ if (start < stop) {
+
+ PathMeasure measure = new PathMeasure(); // todo cached
+ measure.setPath(path, false);
+ float len = measure.getLength();
+ float scaleStart = Math.max(start, 0f) * len;
+ float scaleStop = Math.min(stop, 1f) * len;
+ measure.getSegment(scaleStart, scaleStop, retPath,
+ true);
+ }
+ } else {
+
+ retPath.addPath(path);
+ }
+ }
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 62d58b65e62a..c05ea3d65562 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -254,6 +254,17 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.AbsListViewActivity"
+ android:label="AbsListViewActivity"
+ android:screenOrientation="portrait"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.DatePickerActivity"
android:label="DatePickerActivity"
android:screenOrientation="portrait"
diff --git a/core/tests/coretests/res/layout/activity_abslist_view.xml b/core/tests/coretests/res/layout/activity_abslist_view.xml
new file mode 100644
index 000000000000..85b4f11fb9ad
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_abslist_view.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <view
+ class="android.widget.AbsListViewFunctionalTest$MyListView"
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+</LinearLayout> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 0c1e8793bfc9..38288dcb3b9d 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -421,6 +421,47 @@ public class ResourcesManagerTest extends TestCase {
ResourcesManager.setInstance(oriResourcesManager);
}
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ Resources old_resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(old_resources);
+ ResourcesImpl oldImpl = old_resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ // Create another resources with identical parameters.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+ // For a normal ResourcesImpl redirect, new Resources may find an old ResourcesImpl cache
+ // and reuse it based on the ResourcesKey. But for shared library ResourcesImpl redirect,
+ // new created Resources should never reuse any old impl, it has to recreate a new impl
+ // which has proper asset paths appended.
+ assertNotSame(oldImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
for (int i = 0; i < resourcePaths.length; i++) {
if (!resourcePaths[i].endsWith(".apk")) {
diff --git a/core/tests/coretests/src/android/widget/AbsListViewActivity.java b/core/tests/coretests/src/android/widget/AbsListViewActivity.java
new file mode 100644
index 000000000000..a617fa433a53
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AbsListViewActivity.java
@@ -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.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the AbsListView widget.
+ */
+public class AbsListViewActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_abslist_view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
new file mode 100644
index 000000000000..ceea6caf410b
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.widget;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class AbsListViewFunctionalTest {
+ private final String[] mCountryList = new String[] {
+ "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus",
+ "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
+ "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
+ "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
+ "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
+ "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
+ "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
+ };
+ private AbsListViewActivity mActivity;
+ private MyListView mMyListView;
+
+ @Rule
+ public ActivityTestRule<AbsListViewActivity> mActivityRule = new ActivityTestRule<>(
+ AbsListViewActivity.class);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = mActivityRule.getActivity();
+ mMyListView = (MyListView) mActivity.findViewById(R.id.list_view);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testLsitViewSetVelocity() throws Throwable {
+ final ArrayList<String> items = new ArrayList<>(Arrays.asList(mCountryList));
+ final ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
+ android.R.layout.simple_list_item_1, items);
+
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMyListView,
+ () -> mMyListView.setAdapter(adapter));
+ mActivityRule.runOnUiThread(() -> {
+ // Create an adapter to display the list
+ mMyListView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyListView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyListView.fling(100);
+ });
+ PollingCheck.waitFor(100, () -> mMyListView.isSetVelocityCalled);
+ // set setFrameContentVelocity should be called when fling.
+ assertTrue(mMyListView.isSetVelocityCalled);
+ }
+
+ public static class MyListView extends ListView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyListView(Context context) {
+ super(context);
+ }
+
+ public MyListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
+}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 08977265667c..cf7c5491f787 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -38,6 +38,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -73,8 +74,8 @@ public final class DeviceStateInfoTest {
@Test
public void create() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -87,8 +88,8 @@ public final class DeviceStateInfoTest {
@Test
public void equals() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -100,15 +101,14 @@ public final class DeviceStateInfoTest {
Assert.assertEquals(info, sameInfo);
final DeviceStateInfo differentInfo = new DeviceStateInfo(
- List.of(DEVICE_STATE_0, DEVICE_STATE_2), baseState,
- currentState);
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_2)), baseState, currentState);
assertNotEquals(info, differentInfo);
}
@Test
public void diff_sameObject() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -118,10 +118,10 @@ public final class DeviceStateInfoTest {
@Test
public void diff_differentSupportedStates() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
- DEVICE_STATE_0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_2),
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
DEVICE_STATE_0, DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_2)), DEVICE_STATE_0, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -130,10 +130,10 @@ public final class DeviceStateInfoTest {
@Test
public void diff_differentNonOverrideState() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_1,
- DEVICE_STATE_0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
- DEVICE_STATE_2, DEVICE_STATE_0);
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
+ DEVICE_STATE_1, DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_2, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -142,10 +142,10 @@ public final class DeviceStateInfoTest {
@Test
public void diff_differentState() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
- DEVICE_STATE_1);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
- DEVICE_STATE_0, DEVICE_STATE_2);
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
+ DEVICE_STATE_0, DEVICE_STATE_1);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_2);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -154,8 +154,8 @@ public final class DeviceStateInfoTest {
@Test
public void writeToParcel() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState nonOverrideState = DEVICE_STATE_0;
final DeviceState state = DEVICE_STATE_2;
final DeviceStateInfo originalInfo =
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index ee238c0a5533..f4d363167a75 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -40,6 +40,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -276,7 +277,7 @@ public final class DeviceStateManagerGlobalTest {
new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build());
final DeviceState state = new DeviceState(
new DeviceState.Configuration.Builder(mergedState, "" /* name */).build());
- return new DeviceStateInfo(mSupportedDeviceStates, baseState, state);
+ return new DeviceStateInfo(new ArrayList<>(mSupportedDeviceStates), baseState, state);
}
private void notifyDeviceStateInfoChanged() {
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index e441c531a4c9..199e9293ad65 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -120,24 +120,6 @@ public final class FontFamily {
}
/**
- * Returns true if the passed font files can be used for building a variable font family
- * that automatically adjust the `wght` and `ital` axes value for the requested
- * weight/italic style values.
- *
- * This method can be used for checking that the provided font files can be used for
- * building a variable font family created with {@link #buildVariableFamily()}.
- * If this function returns false, the {@link #buildVariableFamily()} will fail and
- * return null.
- *
- * @return true if a variable font can be built from the given fonts. Otherwise, false.
- */
- @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
- public boolean canBuildVariableFamily() {
- int variableFamilyType = analyzeAndResolveVariableType(mFonts);
- return variableFamilyType != VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
- }
-
- /**
* Build a variable font family that automatically adjust the `wght` and `ital` axes value
* for the requested weight/italic style values.
*
@@ -158,9 +140,11 @@ public final class FontFamily {
* value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
* value of the font is ignored.
*
- * If none of the above conditions are met, this function return {@code null}. Please check
- * that your font files meet the above requirements or consider using the {@link #build()}
- * method.
+ * If none of the above conditions are met, the provided font files cannot be used for
+ * variable font family and this function returns {@code null}. Even if this function
+ * returns {@code null}, you can still use {@link #build()} method for creating FontFamily
+ * instance with manually specifying variation settings by using
+ * {@link Font.Builder#setFontVariationSettings(String)}.
*
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 9b14ce467662..b749a06bd516 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -165,15 +165,27 @@ java_library {
},
}
+filegroup {
+ name: "wm_shell-shared-aidls",
+
+ srcs: [
+ "shared/**/*.aidl",
+ ],
+
+ path: "shared/src",
+}
+
java_library {
name: "WindowManager-Shell-shared",
srcs: [
"shared/**/*.java",
"shared/**/*.kt",
+ ":wm_shell-shared-aidls",
],
static_libs: [
"androidx.dynamicanimation_dynamicanimation",
+ "jsr330",
],
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl
index 72fba3bb7de4..8481c446c6aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
index 7f4a8f1d476a..526407e25d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.view.SurfaceControl;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
-import com.android.wm.shell.transition.IHomeTransitionListener;
+import com.android.wm.shell.shared.IHomeTransitionListener;
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
index da39017a0313..5e49f559ca64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.annotation.NonNull;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface to manage remote transitions.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
new file mode 100644
index 000000000000..a1496ac1d33b
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
@@ -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 com.android.wm.shell.shared.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
+ * instead of the app vsync.
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ChoreographerSfVsync {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java
index 9ac7a12bc509..52a717b3a60c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java
new file mode 100644
index 000000000000..ae5188cf8093
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java
@@ -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 com.android.wm.shell.shared.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or class that is called from an external thread to the Shell threads. */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExternalThread {}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java
new file mode 100644
index 000000000000..bd2887e39ef1
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java
@@ -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 com.android.wm.shell.shared.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellAnimationThread {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java
index 4cd3c903f2f8..586ac8297e26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java
new file mode 100644
index 000000000000..6c879a491fe0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java
@@ -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 com.android.wm.shell.shared.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellMainThread {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java
index c2fd54fd96d7..4887dbe81b25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8d8dc10951a6..26432111efdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -20,7 +20,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.window.BackEvent;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface for external process to get access to the Back animation related methods.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 9b9798c6d93b..ad3be3d0b022 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -69,8 +69,8 @@ import com.android.wm.shell.animation.FlingAnimationUtils;
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.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 7561a266c5ec..3253cac23ce8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -42,8 +42,8 @@ import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
-import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index cfd9fb613414..cae2e80d5320 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -49,7 +49,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index fcf500a60166..e33aa7568d09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -54,7 +54,7 @@ import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
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 4455a3caa7d9..ce8a460f10d9 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
@@ -101,14 +101,14 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 26077cf7057b..127a49fc7875 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,8 +37,8 @@ import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index b828aac39040..2873d58439cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -28,7 +28,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 8353900be0ef..dcbc72ab0d32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -34,7 +34,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index ca06024a9adb..55dc793cc3b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -30,7 +30,7 @@ import android.view.inputmethod.ImeTracker;
import androidx.annotation.BinderThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index 53683c67d825..43c92cab6a68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -33,7 +33,7 @@ import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
deleted file mode 100644
index 4009ad21b9b8..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
- * instead of the app vsync.
- */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ChoreographerSfVsync {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
deleted file mode 100644
index 7560f71d1f98..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or class that is called from an external thread to the Shell threads. */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ExternalThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
deleted file mode 100644
index 0479f8780c79..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellAnimationThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
deleted file mode 100644
index 423f4ce3bfd4..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellMainThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
index 317e48e19c13..c421dec025f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
import java.util.Map;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index fa2e23647a39..cf3ad4299cea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -24,8 +24,8 @@ import android.provider.DeviceConfig;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 216da070754b..011093718671 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -31,10 +31,9 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.pip.TvPipModule;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.tv.TvSplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
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 512211460753..73228de83c0f 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
@@ -58,10 +58,6 @@ import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -92,6 +88,11 @@ import com.android.wm.shell.performance.PerfHintController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -106,7 +107,6 @@ import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.HomeTransitionObserver;
-import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 0cc545a7724a..c5644a8f6876 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -33,11 +33,11 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalMainThread;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.annotations.ExternalMainThread;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import dagger.Module;
import dagger.Provides;
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 04f0f44d2876..b933e5d70586 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
@@ -52,9 +52,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
@@ -77,6 +74,9 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 1e3d7fb06da2..d644006cde81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -29,7 +29,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -56,6 +55,7 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 458ea05e620d..ae07812a2427 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -25,7 +25,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -38,6 +37,7 @@ import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 54c2aeab4976..8d1b15c1e631 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -29,7 +29,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -53,6 +52,7 @@ import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
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 5889da12d6e9..df1b06225fda 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
@@ -18,7 +18,7 @@ package com.android.wm.shell.desktopmode;
import android.graphics.Region;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
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 1b1c96764e88..c369061810d0 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
@@ -61,8 +61,6 @@ import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.annotations.ExternalThread
-import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
@@ -72,6 +70,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.annotations.ExternalThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 7da1b23dd5b1..165feec58455 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -67,8 +67,8 @@ import com.android.wm.shell.common.DisplayController;
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.annotations.ExternalMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 73de231fb63a..863a51ad575b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -48,8 +48,8 @@ import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index 33c299f0b161..4215b2cc5f29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.keyguard;
import android.annotation.NonNull;
import android.window.IRemoteTransition;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface exposed to SystemUI Keyguard to register handlers for running
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 2ee334873780..b000e3228b9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -18,7 +18,7 @@ package com.android.wm.shell.onehanded;
import android.os.SystemProperties;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface to engage one handed feature.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 679d4ca2ac48..39b9000856f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -55,7 +55,7 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index a9aa6badcfe2..7b1ef5c6cddd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -18,7 +18,7 @@ package com.android.wm.shell.pip;
import android.graphics.Rect;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index bd186ba22588..cdeb00b7b9e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -82,7 +82,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -92,6 +91,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2616b8b08bf1..eebd13370321 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.List;
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 370720746808..f9fcfacf47f9 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
@@ -49,11 +49,11 @@ 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.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2b433e9c4227..576219769e61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -21,8 +21,8 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.graphics.Rect;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3e34c303e161..c3261bbc7351 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -90,7 +90,6 @@ 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.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
@@ -99,6 +98,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 4465aef0258d..3353c7bd81c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -47,8 +47,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 2f6edc226c45..5ced1fb41a41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -45,7 +45,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
index a7e4b0119480..f0a2315d7deb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.taskview;
import android.annotation.UiContext;
import android.content.Context;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index 7eed5883043d..e4fcff0c372a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -22,7 +22,7 @@ import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index cb2944c120e0..c9185ae39114 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -32,6 +32,7 @@ import android.window.TransitionInfo;
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.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.TransitionUtil;
/**
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 a77602b3d2d0..437a00e4a160 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
@@ -76,10 +76,13 @@ 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.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.IHomeTransitionListener;
+import com.android.wm.shell.shared.IShellTransitions;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 66efa02de764..e7d37addb368 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 1905fa8ce612..82d43bcaaec9 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -65,6 +65,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
/**
MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
@@ -2014,6 +2015,23 @@ final public class MediaCodec {
}
}
+ // HACKY(b/325389296): aconfig flag accessors may not work in all contexts where MediaCodec API
+ // is used, so allow accessors to fail. In those contexts use a default value, normally false.
+
+ /* package private */
+ static boolean GetFlag(Supplier<Boolean> flagValueSupplier) {
+ return GetFlag(flagValueSupplier, false /* defaultValue */);
+ }
+
+ /* package private */
+ static boolean GetFlag(Supplier<Boolean> flagValueSupplier, boolean defaultValue) {
+ try {
+ return flagValueSupplier.get();
+ } catch (java.lang.RuntimeException e) {
+ return defaultValue;
+ }
+ }
+
private boolean mHasSurface = false;
/**
@@ -2346,7 +2364,7 @@ final public class MediaCodec {
}
// at the moment no codecs support detachable surface
- if (android.media.codec.Flags.nullOutputSurface()) {
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
// Detached surface flag is only meaningful if surface is null. Otherwise, it is
// ignored.
if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index abad46046890..8ff4305a9817 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@ import static android.media.codec.Flags.FLAG_HLG_EDITING;
import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.MediaCodec.GetFlag;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -827,10 +828,10 @@ public final class MediaCodecInfo {
features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false));
features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
features.add(new Feature(FEATURE_LowLatency, (1 << 7), true));
- if (android.media.codec.Flags.dynamicColorAspects()) {
+ if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
}
- if (android.media.codec.Flags.nullOutputSurface()) {
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));
}
@@ -851,10 +852,10 @@ public final class MediaCodecInfo {
features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
- if (android.media.codec.Flags.hlgEditing()) {
+ if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
}
- if (android.media.codec.Flags.regionOfInterest()) {
+ if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
features.add(new Feature(FEATURE_Roi, (1 << 7), true));
}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index d6d74e8a087a..94fce797f5d6 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -122,9 +122,6 @@ cc_library_shared {
"-Wunused",
"-Wunreachable-code",
],
-
- // TODO(b/330503129) Workaround build breakage.
- lto_O0: true,
}
cc_library_shared {
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 7caa9e4863f9..cf5059ceb3c9 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -44,7 +44,4 @@ cc_library_shared {
"-Wunreachable-code",
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
-
- // TODO(b/330503129) Workaround LTO build breakage.
- lto_O0: true,
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 976a3ad69901..bcc737a351a9 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -30,6 +30,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -46,6 +48,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -135,10 +138,10 @@ public class PackageUtil {
static final class AppSnippet implements Parcelable {
@NonNull public CharSequence label;
- @Nullable public Drawable icon;
+ @NonNull public Drawable icon;
public int iconSize;
- AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) {
+ AppSnippet(@NonNull CharSequence label, @NonNull Drawable icon, Context context) {
this.label = label;
this.icon = icon;
final ActivityManager am = context.getSystemService(ActivityManager.class);
@@ -147,14 +150,15 @@ public class PackageUtil {
private AppSnippet(Parcel in) {
label = in.readString();
- Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
+ byte[] b = in.readBlob();
+ Bitmap bmp = BitmapFactory.decodeByteArray(b, 0, b.length);
icon = new BitmapDrawable(Resources.getSystem(), bmp);
iconSize = in.readInt();
}
@Override
public String toString() {
- return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
+ return "AppSnippet[" + label + " (has icon)]";
}
@Override
@@ -165,16 +169,18 @@ public class PackageUtil {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(label.toString());
+
Bitmap bmp = getBitmapFromDrawable(icon);
- dest.writeParcelable(bmp, 0);
+ dest.writeBlob(getBytesFromBitmap(bmp));
+ bmp.recycle();
+
dest.writeInt(iconSize);
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
// Create an empty bitmap with the dimensions of our drawable
final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
// Associate it with a canvas. This canvas will draw the icon on the bitmap
final Canvas canvas = new Canvas(bmp);
// Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
@@ -192,6 +198,23 @@ public class PackageUtil {
return bmp;
}
+ private byte[] getBytesFromBitmap(Bitmap bmp) {
+ ByteArrayOutputStream baos = null;
+ try {
+ baos = new ByteArrayOutputStream();
+ bmp.compress(CompressFormat.PNG, 100, baos);
+ } finally {
+ try {
+ if (baos != null) {
+ baos.close();
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "ByteArrayOutputStream was not closed");
+ }
+ }
+ return baos.toByteArray();
+ }
+
public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
public AppSnippet createFromParcel(Parcel in) {
return new AppSnippet(in);
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
new file mode 100644
index 000000000000..c7fbb5f0374b
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
new file mode 100644
index 000000000000..a2b964882c7c
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
@@ -0,0 +1,74 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:ellipsize="marquee" />
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@+id/settingslib_learn_more"
+ android:text="@string/settingslib_learn_more_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingBottom="8dp"
+ android:clickable="true"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
new file mode 100644
index 000000000000..fff41c3583f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
@@ -0,0 +1,24 @@
+<?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>
+ <style name="TextAppearance.TopIntroText"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8a5dfefa18c3..69e4bd7afdd2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1030,13 +1030,6 @@
<!-- Settings item title to select whether to disable cache for transcoding. [CHAR LIMIT=85] -->
<string name="transcode_disable_cache">Disable transcoding cache</string>
- <!-- Developer settings title: widevine settings screen. [CHAR LIMIT=50] -->
- <string name="widevine_settings_title">Widevine settings</string>
- <!-- Developer settings title: select whether to enable Force L3 fallback. [CHAR LIMIT=50] -->
- <string name="force_l3_fallback_title">Force L3 fallback</string>
- <!-- Developer settings summary: select whether to enable Force L3 fallback.[CHAR LIMIT=NONE] -->
- <string name="force_l3_fallback_summary">Select to force L3 fallback</string>
-
<!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
<string name="runningservices_settings_title">Running services</string>
<!-- Services settings screen, setting option summary for the user to go to the screen to view running services -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2889ce26d65d..56118dae3f96 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -51,9 +51,11 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.Formatter;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
@@ -990,11 +992,22 @@ public class ApplicationsState {
apps = new ArrayList<>(mAppEntries);
}
+ ArrayMap<UserHandle, Boolean> profileHideInQuietModeStatus = new ArrayMap<>();
ArrayList<AppEntry> filteredApps = new ArrayList<>();
if (DEBUG) {
Log.i(TAG, "Rebuilding...");
}
for (AppEntry entry : apps) {
+ if (android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
+ UserHandle userHandle = UserHandle.of(UserHandle.getUserId(entry.info.uid));
+ if (!profileHideInQuietModeStatus.containsKey(userHandle)) {
+ profileHideInQuietModeStatus.put(
+ userHandle, isHideInQuietEnabledForProfile(mUm, userHandle));
+ }
+ filter.refreshAppEntryOnRebuild(
+ entry, profileHideInQuietModeStatus.get(userHandle));
+ }
if (entry != null && (filter == null || filter.filterApp(entry))) {
synchronized (mEntriesMap) {
if (DEBUG_LOCKING) {
@@ -1648,6 +1661,11 @@ public class ApplicationsState {
*/
public boolean isHomeApp;
+ /**
+ * Whether the app should be hidden for user when quiet mode is enabled.
+ */
+ public boolean hideInQuietMode;
+
public String getNormalizedLabel() {
if (normalizedLabel != null) {
return normalizedLabel;
@@ -1691,6 +1709,7 @@ public class ApplicationsState {
UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
mProfileType = userInfo.userType;
this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+ hideInQuietMode = shouldHideInQuietMode(um, info.uid);
}
public boolean isClonedProfile() {
@@ -1800,12 +1819,32 @@ public class ApplicationsState {
this.labelDescription = this.label;
}
}
+
+ /**
+ * Returns true if profile is in quiet mode and the profile should not be visible when the
+ * quiet mode is enabled, false otherwise.
+ */
+ private boolean shouldHideInQuietMode(@NonNull UserManager userManager, int uid) {
+ if (android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
+ UserHandle userHandle = UserHandle.of(UserHandle.getUserId(uid));
+ return isHideInQuietEnabledForProfile(userManager, userHandle);
+ }
+ return false;
+ }
}
private static boolean hasFlag(int flags, int flag) {
return (flags & flag) != 0;
}
+ private static boolean isHideInQuietEnabledForProfile(
+ UserManager userManager, UserHandle userHandle) {
+ return userManager.isQuietModeEnabled(userHandle)
+ && userManager.getUserProperties(userHandle).getShowInQuietMode()
+ == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
+ }
+
/**
* Compare by label, then package name, then uid.
*/
@@ -1868,6 +1907,15 @@ public class ApplicationsState {
}
boolean filterApp(AppEntry info);
+
+ /**
+ * Updates AppEntry based on whether quiet mode is enabled and should not be
+ * visible for the corresponding profile.
+ */
+ default void refreshAppEntryOnRebuild(
+ @NonNull AppEntry appEntry,
+ boolean hideInQuietMode) {
+ }
}
public static final AppFilter FILTER_PERSONAL = new AppFilter() {
@@ -2010,6 +2058,25 @@ public class ApplicationsState {
}
};
+ public static final AppFilter FILTER_ENABLED_NOT_QUIET = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(@NonNull AppEntry entry) {
+ return entry.info.enabled && !AppUtils.isInstant(entry.info)
+ && !entry.hideInQuietMode;
+ }
+
+ @Override
+ public void refreshAppEntryOnRebuild(
+ @NonNull AppEntry appEntry,
+ boolean hideInQuietMode) {
+ appEntry.hideInQuietMode = hideInQuietMode;
+ }
+ };
+
public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
@Override
public void init() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index fe83ffb094e2..b9748883b25d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -267,6 +267,16 @@ public class ApplicationsStateTest {
}
@Test
+ public void testEnabledFilterNotQuietRejectsInstantApp() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE);
+ mEntry.info.enabled = true;
+ assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isTrue();
+ when(mEntry.info.isInstantApp()).thenReturn(true);
+ assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isFalse();
+ }
+
+ @Test
public void testFilterWithDomainUrls() {
mEntry.info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
// should included updated system apps
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index f70ad9ed58b0..5f3d1eafb4fb 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -62,6 +62,7 @@ import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -452,6 +453,8 @@ public class AccessibilityMenuServiceTest {
}
@Test
+ @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
+ + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 503779c1801b..ed8027756f47 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -482,7 +482,7 @@ private fun EmptyStateCta(
Card(
modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, colors.primaryFixedDim),
+ border = BorderStroke(3.dp, colors.secondary),
shape = RoundedCornerShape(size = 80.dp)
) {
Column(
@@ -495,7 +495,7 @@ private fun EmptyStateCta(
text = stringResource(R.string.title_for_empty_state_cta),
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
- color = colors.secondaryFixed,
+ color = colors.secondary,
)
Row(
modifier = Modifier.fillMaxWidth(),
@@ -505,8 +505,8 @@ private fun EmptyStateCta(
modifier = Modifier.height(56.dp),
colors =
ButtonDefaults.buttonColors(
- containerColor = colors.primaryFixed,
- contentColor = colors.onPrimaryFixed,
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
),
onClick = {
viewModel.onOpenWidgetEditor(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index c5c48e6d4b6b..fcd77609768e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -114,6 +114,11 @@ fun SceneScope.CollapsedShadeHeader(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val isDisabled by viewModel.isDisabled.collectAsState()
+ if (isDisabled) {
+ return
+ }
+
val formatProgress =
animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
.unsafeCompositionState(initialValue = 0f)
@@ -251,6 +256,11 @@ fun SceneScope.ExpandedShadeHeader(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val isDisabled by viewModel.isDisabled.collectAsState()
+ if (isDisabled) {
+ return
+ }
+
val formatProgress =
animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
.unsafeCompositionState(initialValue = 1f)
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 25c649a7d4fb..e086cda2f793 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
@@ -27,7 +27,9 @@ import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
+import android.util.TypedValue
import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
@@ -42,6 +44,7 @@ import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
+import kotlin.math.min
/**
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
@@ -85,6 +88,8 @@ class AnimatableClockView @JvmOverloads constructor(
private var textAnimator: TextAnimator? = null
private var onTextAnimatorInitialized: Runnable? = null
+ // last text size which is not constrained by view height
+ private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
{ layout, invalidateCb ->
TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
@@ -188,6 +193,11 @@ class AnimatableClockView @JvmOverloads constructor(
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure")
+ if (migratedClocks && !isSingleLineInternal &&
+ MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 61089049bf89..ef385673c950 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -97,6 +98,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 05c81496c3dc..efbdb7d466d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -242,6 +242,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 062741df01cf..4c573d3b83ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -13,6 +13,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -69,6 +70,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index d1c4ec3ddacf..f90a3b14f968 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -110,6 +110,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
@@ -280,17 +281,17 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
@Test
fun upTransitionSceneKey_customizing_noTransition() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
-
- qsSceneAdapter.setCustomizing(true)
- assertThat(
- destinationScenes!!
- .keys
- .filterIsInstance<Swipe>()
- .filter { it.direction == SwipeDirection.Up }
- ).isEmpty()
- }
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+
+ qsSceneAdapter.setCustomizing(true)
+ assertThat(
+ destinationScenes!!.keys.filterIsInstance<Swipe>().filter {
+ it.direction == SwipeDirection.Up
+ }
+ )
+ .isEmpty()
+ }
@Test
fun shadeMode() =
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index c99cb39f91bf..83658d3c1984 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -129,6 +129,11 @@ public interface BcSmartspaceDataPlugin extends Plugin {
void setDozeAmount(float amount);
/**
+ * Set if the screen is on.
+ */
+ default void setScreenOn(boolean screenOn) {}
+
+ /**
* Set if dozing is true or false
*/
default void setDozing(boolean dozing) {}
diff --git a/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
new file mode 100644
index 000000000000..edfbe7bed588
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@*android:dimen/rounded_corner_radius" />
+ <!-- Since the device corners are not perfectly rounded, we create the stroke with offset
+ to fill up the space between border and device corner -->
+ <stroke
+ android:color="@color/magnification_border_color"
+ android:width="@dimen/magnifier_border_width_fullscreen_with_offset"/>
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fullscreen_magnification_border.xml b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
new file mode 100644
index 000000000000..5f738c041f54
--- /dev/null
+++ b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnification_fullscreen_border"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false"
+ android:background="@drawable/accessibility_fullscreen_magnification_border_background"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e004ee9fa157..29f4942e8b4a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1266,6 +1266,8 @@
<dimen name="magnifier_corner_radius">28dp</dimen>
<dimen name="magnifier_edit_corner_radius">16dp</dimen>
<dimen name="magnifier_edit_outer_corner_radius">18dp</dimen>
+ <dimen name="magnifier_border_width_fullscreen_with_offset">12dp</dimen>
+ <dimen name="magnifier_border_width_fullscreen">6dp</dimen>
<dimen name="magnifier_border_width">8dp</dimen>
<dimen name="magnifier_stroke_width">2dp</dimen>
<dimen name="magnifier_edit_dash_gap">20dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 751a3f8458bd..68d2eb358105 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -22,6 +22,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
@@ -138,11 +139,15 @@ public class Utilities {
/** @return whether or not {@param context} represents that of a large screen device or not */
@TargetApi(Build.VERSION_CODES.R)
public static boolean isLargeScreen(Context context) {
- final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources());
+ }
+
+ /** @return whether or not {@param context} represents that of a large screen device or not */
+ public static boolean isLargeScreen(WindowManager windowManager, Resources resources) {
final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
- context.getResources().getConfiguration().densityDpi);
+ resources.getConfiguration().densityDpi);
return smallestWidth >= TABLET_MIN_DPS;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 1a9b01fd4996..7e94804908b6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -29,8 +29,8 @@ import com.android.systemui.res.R;
import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
new file mode 100644
index 000000000000..af8149f59a16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -0,0 +1,140 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import android.annotation.UiContext;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.AttachedSurfaceControl;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.UiThread;
+
+import com.android.systemui.res.R;
+
+import java.util.function.Supplier;
+
+class FullscreenMagnificationController {
+
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ private final WindowManager mWindowManager;
+ private Supplier<SurfaceControlViewHost> mScvhSupplier;
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private Rect mWindowBounds;
+ private SurfaceControl.Transaction mTransaction;
+ private View mFullscreenBorder = null;
+ private int mBorderOffset;
+ private final int mDisplayId;
+ private static final Region sEmptyRegion = new Region();
+
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier) {
+ mContext = context;
+ mAccessibilityManager = accessibilityManager;
+ mWindowManager = windowManager;
+ mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mTransaction = new SurfaceControl.Transaction();
+ mScvhSupplier = scvhSupplier;
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ mDisplayId = mContext.getDisplayId();
+ }
+
+ @UiThread
+ void onFullscreenMagnificationActivationChanged(boolean activated) {
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
+ }
+
+ @UiThread
+ private void removeFullscreenMagnificationBorder() {
+ if (mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ mSurfaceControlViewHost = null;
+ }
+
+ if (mFullscreenBorder != null) {
+ mFullscreenBorder = null;
+ }
+ }
+
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
+ @UiThread
+ private void createFullscreenMagnificationBorder() {
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+
+ SurfaceControl surfaceControl = mSurfaceControlViewHost
+ .getSurfacePackage().getSurfaceControl();
+
+ mTransaction
+ .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(surfaceControl, Integer.MAX_VALUE)
+ .show(surfaceControl)
+ .apply();
+
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+
+ applyTouchableRegion();
+ }
+
+ private LayoutParams getBorderLayoutParams() {
+ LayoutParams params = new LayoutParams(
+ mWindowBounds.width() + 2 * mBorderOffset,
+ mWindowBounds.height() + 2 * mBorderOffset,
+ LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+ LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.setTrustedOverlay();
+ return params;
+ }
+
+ private void applyTouchableRegion() {
+ // Sometimes this can get posted and run after deleteWindowMagnification() is called.
+ if (mFullscreenBorder == null) return;
+
+ AttachedSurfaceControl surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl();
+
+ // The touchable region of the mFullscreenBorder will be empty since we are going to allow
+ // all touch events to go through this view.
+ surfaceControl.setTouchableRegion(sEmptyRegion);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 88fa2de186d0..70165f377a7c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -34,6 +34,7 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IMagnificationConnection;
@@ -134,6 +135,37 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@VisibleForTesting
DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
+ private static class FullscreenMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+ private final Context mContext;
+
+ FullscreenMagnificationControllerSupplier(Context context, Handler handler,
+ DisplayManager displayManager, SysUiState sysUiState,
+ SecureSettings secureSettings) {
+ super(displayManager);
+ mContext = context;
+ }
+
+ @Override
+ protected FullscreenMagnificationController createInstance(Display display) {
+ final Context windowContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
+ mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
+ windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+ return new FullscreenMagnificationController(
+ windowContext,
+ windowContext.getSystemService(AccessibilityManager.class),
+ windowContext.getSystemService(WindowManager.class),
+ scvhSupplier);
+ }
+ }
+
+ @VisibleForTesting
+ DisplayIdIndexSupplier<FullscreenMagnificationController>
+ mFullscreenMagnificationControllerSupplier;
+
private static class SettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
@@ -185,6 +217,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
+ mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
+ context, mHandler, displayManager, sysUiState, secureSettings);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
@@ -273,8 +307,13 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
}
}
+ @MainThread
void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
- // Do nothing
+ final FullscreenMagnificationController fullscreenMagnificationController =
+ mFullscreenMagnificationControllerSupplier.get(displayId);
+ if (fullscreenMagnificationController != null) {
+ fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
+ }
}
@MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index 35fe6b14ee28..c464c82cb90f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -22,7 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.IntDef;
import android.content.ComponentCallbacks;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -37,7 +36,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
-import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -171,9 +169,9 @@ class MenuMessageView extends LinearLayout implements
mTextView.setTextColor(textColor);
mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
- final ColorStateList colorAccent = Utils.getColorAccent(getContext());
mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
- mUndoButton.setTextColor(colorAccent);
+ mUndoButton.setTextColor(textColor);
+ mUndoButton.setAllCaps(true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 3b0c281a7057..e104166935c1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -36,11 +36,11 @@ import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
-import com.android.wm.shell.transition.ShellTransitions;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index dfec771ced26..e04a0e542855 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,7 +23,6 @@ import androidx.annotation.Nullable;
import com.android.systemui.SystemUIInitializer;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -32,11 +31,12 @@ import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
-import com.android.wm.shell.transition.ShellTransitions;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 19a44ccdb554..d33e7fffef98 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -394,10 +394,6 @@ object Flags {
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
- /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
- @JvmField
- val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot")
-
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 3134e35a92e3..6b53f4ed554f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -84,8 +84,8 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 53c81e537708..cf8358203b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -20,7 +20,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.WallpaperManager
-import android.content.Context
+import android.content.res.Resources
import android.graphics.Matrix
import android.graphics.Rect
import android.os.DeadObjectException
@@ -32,16 +32,18 @@ import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
+import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.core.math.MathUtils
import com.android.app.animation.Interpolators
import com.android.internal.R
import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags.fastUnlockTransition
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.Flags.fastUnlockTransition
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.shared.system.ActivityManagerWrapper
@@ -145,17 +147,18 @@ const val SURFACE_BEHIND_FADE_OUT_START_DELAY_MS = 0L
*/
@SysUISingleton
class KeyguardUnlockAnimationController @Inject constructor(
- private val context: Context,
- private val keyguardStateController: KeyguardStateController,
- private val
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val
keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -399,7 +402,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
keyguardStateController.addCallback(this)
roundedCornerRadius =
- context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+ resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
/**
@@ -438,7 +441,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
"${!notificationShadeWindowController.isLaunchingActivity}")
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
- Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(context)}")
+ Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
/**
@@ -1100,7 +1103,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
// We don't do the shared element on large screens because the smartspace has to fly across
// large distances, which is distracting.
- if (Utilities.isLargeScreen(context)) {
+ if (Utilities.isLargeScreen(windowManager, resources)) {
return false
}
@@ -1180,8 +1183,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
companion object {
- fun isFoldable(context: Context): Boolean {
- return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
+ fun isFoldable(resources: Resources): Boolean {
+ return resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 881467ff2724..4a09939d0aba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -25,6 +25,7 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.GONE
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
@@ -160,7 +161,7 @@ constructor(
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(R.id.lockscreen_clock_view_large, END, guideline, END)
- connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
context.resources.getDimensionPixelSize(
@@ -172,7 +173,7 @@ constructor(
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
- constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, MATCH_CONSTRAINT)
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 3081f89d0cb9..922997d08c25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -18,32 +18,11 @@ package com.android.systemui.screenshot
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.app.tracing.coroutines.launch
-import kotlinx.coroutines.CoroutineScope
-import java.util.function.Consumer
-import javax.inject.Inject
-
-/** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
- /**
- * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
- *
- * @param screenshot the screenshot to process
- */
- suspend fun process(screenshot: ScreenshotData): ScreenshotData
-}
/** Implementation of [ScreenshotRequestProcessor] */
-@SysUISingleton
-class RequestProcessor
-@Inject
-constructor(
+class RequestProcessor(
private val capture: ImageCapture,
private val policy: ScreenshotPolicy,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -78,24 +57,6 @@ constructor(
return result
}
-
- /**
- * Note: This is for compatibility with existing Java. Prefer the suspending function when
- * calling from a Coroutine context.
- *
- * @param screenshot the screenshot to process
- * @param callback the callback to provide the processed screenshot, invoked from the main
- * thread
- */
- fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
- mainScope.launch({ "$TAG#processAsync" }) {
- val result = process(screenshot)
- callback.accept(result)
- }
- }
}
private const val TAG = "RequestProcessor"
-
-/** Exception thrown by [RequestProcessor] if something goes wrong. */
-class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
index d874eb68460b..4079abe41a7d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -105,7 +105,7 @@ internal constructor(
/** Factory for [ScreenshotNotificationsController]. */
@AssistedFactory
- interface Factory {
- fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+ fun interface Factory {
+ fun create(displayId: Int): ScreenshotNotificationsController
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index d5ab3066bfde..c8067df114fc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
-internal class ScreenshotProxyService
+class ScreenshotProxyService
@Inject
constructor(
private val mExpansionMgr: ShadeExpansionStateManager,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
new file mode 100644
index 000000000000..796457d88cc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.screenshot
+
+/** Processes a screenshot request sent from [ScreenshotHelper]. */
+interface ScreenshotRequestProcessor {
+ /**
+ * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
+ *
+ * @param screenshot the screenshot to process
+ */
+ suspend fun process(screenshot: ScreenshotData): ScreenshotData
+}
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index bc3375502dd4..92d3e550dc0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,7 +5,6 @@ import android.os.Trace
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.app.tracing.coroutines.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
@@ -18,6 +17,23 @@ import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+interface TakeScreenshotExecutor {
+ suspend fun executeScreenshots(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: (Uri) -> Unit,
+ requestCallback: RequestCallback
+ )
+ fun onCloseSystemDialogsReceived()
+ fun removeWindows()
+ fun onDestroy()
+ fun executeScreenshotsAsync(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: Consumer<Uri>,
+ requestCallback: RequestCallback
+ )
+}
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -26,7 +42,7 @@ import kotlinx.coroutines.flow.first
* Captures a screenshot for each [Display] available.
*/
@SysUISingleton
-class TakeScreenshotExecutor
+class TakeScreenshotExecutorImpl
@Inject
constructor(
private val screenshotControllerFactory: ScreenshotController.Factory,
@@ -35,7 +51,7 @@ constructor(
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
private val uiEventLogger: UiEventLogger,
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
-) {
+) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
@@ -47,7 +63,7 @@ constructor(
* [onSaved] is invoked only on the default display result. [RequestCallback.onFinish] is
* invoked only when both screenshot UIs are removed.
*/
- suspend fun executeScreenshots(
+ override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
onSaved: (Uri) -> Unit,
requestCallback: RequestCallback
@@ -128,12 +144,8 @@ constructor(
}
}
- /**
- * Propagates the close system dialog signal to all controllers.
- *
- * TODO(b/295143676): Move the receiver in this class once the flag is flipped.
- */
- fun onCloseSystemDialogsReceived() {
+ /** Propagates the close system dialog signal to all controllers. */
+ override fun onCloseSystemDialogsReceived() {
screenshotControllers.forEach { (_, screenshotController) ->
if (!screenshotController.isPendingSharedTransition) {
screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
@@ -142,7 +154,7 @@ constructor(
}
/** Removes all screenshot related windows. */
- fun removeWindows() {
+ override fun removeWindows() {
screenshotControllers.forEach { (_, screenshotController) ->
screenshotController.removeWindow()
}
@@ -151,7 +163,7 @@ constructor(
/**
* Destroys the executor. Afterwards, this class is not expected to work as intended anymore.
*/
- fun onDestroy() {
+ override fun onDestroy() {
screenshotControllers.forEach { (_, screenshotController) ->
screenshotController.onDestroy()
}
@@ -171,12 +183,12 @@ constructor(
}
/** For java compatibility only. see [executeScreenshots] */
- fun executeScreenshotsAsync(
+ override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
onSaved: Consumer<Uri>,
requestCallback: RequestCallback
) {
- mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
+ mainScope.launch {
executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 9cf347bc8569..2187b51be7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,13 +21,11 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
-import static com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import android.annotation.MainThread;
import android.app.Service;
@@ -50,24 +48,19 @@ import android.util.Log;
import android.view.Display;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
-import javax.inject.Provider;
public class TakeScreenshotService extends Service {
private static final String TAG = logTag(TakeScreenshotService.class);
- private final ScreenshotController mScreenshot;
-
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final UiEventLogger mUiEventLogger;
@@ -75,27 +68,20 @@ public class TakeScreenshotService extends Service {
private final Handler mHandler;
private final Context mContext;
private final @Background Executor mBgExecutor;
- private final RequestProcessor mProcessor;
- private final FeatureFlags mFeatureFlags;
+ private final TakeScreenshotExecutor mTakeScreenshotExecutor;
+ @SuppressWarnings("deprecation")
private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
+ if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
if (DEBUG_DISMISS) {
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- // TODO(b/295143676): move receiver inside executor when the flag is enabled.
- mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
- } else if (!mScreenshot.isPendingSharedTransition()) {
- mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
- }
+ mTakeScreenshotExecutor.onCloseSystemDialogsReceived();
}
}
};
- private final Provider<TakeScreenshotExecutor> mTakeScreenshotExecutor;
-
/** Informs about coarse grained state of the Controller. */
public interface RequestCallback {
@@ -111,12 +97,14 @@ public class TakeScreenshotService extends Service {
}
@Inject
- public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
- UserManager userManager, DevicePolicyManager devicePolicyManager,
+ public TakeScreenshotService(
+ UserManager userManager,
+ DevicePolicyManager devicePolicyManager,
UiEventLogger uiEventLogger,
ScreenshotNotificationsController.Factory notificationsControllerFactory,
- Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
- RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
+ Context context,
+ @Background Executor bgExecutor,
+ TakeScreenshotExecutor takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
Log.d(TAG, "new " + this);
}
@@ -127,15 +115,7 @@ public class TakeScreenshotService extends Service {
mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
mContext = context;
mBgExecutor = bgExecutor;
- mFeatureFlags = featureFlags;
- mProcessor = processor;
mTakeScreenshotExecutor = takeScreenshotExecutor;
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mScreenshot = null;
- } else {
- mScreenshot = screenshotControllerFactory.create(
- Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
- }
}
@Override
@@ -161,11 +141,7 @@ public class TakeScreenshotService extends Service {
if (DEBUG_SERVICE) {
Log.d(TAG, "onUnbind");
}
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().removeWindows();
- } else {
- mScreenshot.removeWindow();
- }
+ mTakeScreenshotExecutor.removeWindows();
unregisterReceiver(mCloseSystemDialogs);
return false;
}
@@ -173,11 +149,7 @@ public class TakeScreenshotService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().onDestroy();
- } else {
- mScreenshot.onDestroy();
- }
+ mTakeScreenshotExecutor.onDestroy();
if (DEBUG_SERVICE) {
Log.d(TAG, "onDestroy");
}
@@ -190,6 +162,7 @@ public class TakeScreenshotService extends Service {
mReplyTo = replyTo;
}
+ @Override
public void reportError() {
reportUri(mReplyTo, null);
sendComplete(mReplyTo);
@@ -214,7 +187,6 @@ public class TakeScreenshotService extends Service {
}
@MainThread
- @VisibleForTesting
void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
RequestCallback callback) {
// If the storage for this user is locked, we have no place to store
@@ -245,36 +217,9 @@ public class TakeScreenshotService extends Service {
}
Log.d(TAG, "Processing screenshot data");
-
-
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
- return;
- }
- // TODO(b/295143676): Delete the following after the flag is released.
- try {
- ScreenshotData screenshotData = ScreenshotData.fromRequest(
- request, Display.DEFAULT_DISPLAY);
- mProcessor.processAsync(screenshotData, (data) ->
- dispatchToController(data, onSaved, callback));
-
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to process screenshot request!", e);
- logFailedRequest(request);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- }
+ mTakeScreenshotExecutor.executeScreenshotsAsync(request, onSaved, callback);
}
- // TODO(b/295143676): Delete this.
- private void dispatchToController(ScreenshotData screenshot,
- Consumer<Uri> uriConsumer, RequestCallback callback) {
- mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
- screenshot.getPackageNameString());
- Log.d(TAG, "Screenshot request: " + screenshot);
- mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
- }
private void logFailedRequest(ScreenshotRequest request) {
ComponentName topComponent = request.getTopComponent();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 2ce6d8380e36..6ff0fdaeab0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -26,21 +26,22 @@ import com.android.systemui.screenshot.DefaultScreenshotActionsProvider;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
-import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotActionsProvider;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
-import com.android.systemui.screenshot.ScreenshotProxyService;
-import com.android.systemui.screenshot.ScreenshotRequestProcessor;
import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
import com.android.systemui.screenshot.ScreenshotViewProxy;
+import com.android.systemui.screenshot.TakeScreenshotExecutor;
+import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
+import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
+import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
import dagger.Binds;
@@ -52,7 +53,7 @@ import dagger.multibindings.IntoMap;
/**
* Defines injectable resources for Screenshots
*/
-@Module
+@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class})
public abstract class ScreenshotModule {
@Binds
@@ -61,9 +62,9 @@ public abstract class ScreenshotModule {
abstract Service bindTakeScreenshotService(TakeScreenshotService service);
@Binds
- @IntoMap
- @ClassKey(ScreenshotProxyService.class)
- abstract Service bindScreenshotProxyService(ScreenshotProxyService service);
+ @SysUISingleton
+ abstract TakeScreenshotExecutor bindTakeScreenshotExecutor(
+ TakeScreenshotExecutorImpl impl);
@Binds
abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
@@ -82,10 +83,6 @@ public abstract class ScreenshotModule {
abstract Service bindAppClipsService(AppClipsService service);
@Binds
- abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor(
- RequestProcessor requestProcessor);
-
- @Binds
abstract ScreenshotSoundProvider bindScreenshotSoundProvider(
ScreenshotSoundProviderImpl screenshotSoundProviderImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt
new file mode 100644
index 000000000000..38016ad6b708
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt
@@ -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 com.android.systemui.screenshot.data.model
+
+/** The profile type of a user. */
+enum class ProfileType {
+ /** The user is not a profile. */
+ NONE,
+ /** Private space user */
+ PRIVATE,
+ /** Managed (work) profile. */
+ WORK,
+ /** Cloned apps user */
+ CLONE,
+ /** Communal profile */
+ COMMUNAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt
new file mode 100644
index 000000000000..2c6e4fe765e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.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.screenshot.data.repository
+
+import android.annotation.UserIdInt
+import com.android.systemui.screenshot.data.model.ProfileType
+
+/** A facility for checking user profile types. */
+fun interface ProfileTypeRepository {
+ /**
+ * Returns the profile type when [userId] refers to a profile user. If the profile type is
+ * unknown or not a profile user, [ProfileType.NONE] is returned.
+ */
+ suspend fun getProfileType(@UserIdInt userId: Int): ProfileType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt
new file mode 100644
index 000000000000..42ad21bdf5e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.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.
+ */
+@file:SuppressLint("MissingPermission")
+
+package com.android.systemui.screenshot.data.repository
+
+import android.annotation.SuppressLint
+import android.annotation.UserIdInt
+import android.os.UserManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.data.model.ProfileType
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+/** Fetches profile types from [UserManager] as needed, caching results for a given user. */
+class ProfileTypeRepositoryImpl
+@Inject
+constructor(
+ private val userManager: UserManager,
+ @Background private val background: CoroutineDispatcher
+) : ProfileTypeRepository {
+ /** Cache to avoid repeated requests to IActivityTaskManager for the same userId */
+ private val cache = mutableMapOf<Int, ProfileType>()
+ private val mutex = Mutex()
+
+ override suspend fun getProfileType(@UserIdInt userId: Int): ProfileType {
+ return mutex.withLock {
+ cache[userId]
+ ?: withContext(background) {
+ val userType = userManager.getUserInfo(userId).userType
+ when (userType) {
+ UserManager.USER_TYPE_PROFILE_MANAGED -> ProfileType.WORK
+ UserManager.USER_TYPE_PROFILE_PRIVATE -> ProfileType.PRIVATE
+ UserManager.USER_TYPE_PROFILE_CLONE -> ProfileType.CLONE
+ UserManager.USER_TYPE_PROFILE_COMMUNAL -> ProfileType.COMMUNAL
+ else -> ProfileType.NONE
+ }
+ }
+ .also { cache[userId] = it }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
new file mode 100644
index 000000000000..39b07e3a396a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.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.screenshot.policy
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.RequestProcessor
+import com.android.systemui.screenshot.ScreenshotPolicy
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepositoryImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import javax.inject.Provider
+
+@Module
+interface ScreenshotPolicyModule {
+
+ @Binds
+ @SysUISingleton
+ fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun bindScreenshotRequestProcessor(
+ imageCapture: ImageCapture,
+ policyProvider: Provider<ScreenshotPolicy>,
+ ): ScreenshotRequestProcessor {
+ return RequestProcessor(imageCapture, policyProvider.get())
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt
new file mode 100644
index 000000000000..e3eb3c4de80a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.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.screenshot.proxy
+
+/**
+ * Provides a mechanism to interact with the main SystemUI process.
+ *
+ * ScreenshotService runs in an isolated process. Because of this, interactions with an outside
+ * component with shared state must be accessed through this proxy to reach the correct instance.
+ *
+ * TODO: Rename and relocate 'ScreenshotProxyService' to this package and remove duplicate clients.
+ */
+interface SystemUiProxy {
+ /** Indicate if the notification shade is "open"... (not in the fully collapsed position) */
+ suspend fun isNotificationShadeExpanded(): Boolean
+
+ /**
+ * Request keyguard dismissal, raising keyguard credential entry if required and waits for
+ * completion.
+ */
+ suspend fun dismissKeyguard()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt
new file mode 100644
index 000000000000..dcf58bde5f70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.screenshot.proxy
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.screenshot.IOnDoneCallback
+import com.android.systemui.screenshot.IScreenshotProxy
+import com.android.systemui.screenshot.ScreenshotProxyService
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CompletableDeferred
+
+private const val TAG = "SystemUiProxy"
+
+/** An implementation of [SystemUiProxy] using [ScreenshotProxyService]. */
+class SystemUiProxyClient @Inject constructor(@Application context: Context) : SystemUiProxy {
+ @SuppressLint("ImplicitSamInstance")
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ override suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+ proxyConnector
+ .postForResult { it.isNotificationShadeExpanded }
+ .whenComplete { expanded, error ->
+ error?.also { Log.wtf(TAG, "isNotificationShadeExpanded", it) }
+ k.resume(expanded ?: false)
+ }
+ }
+
+ override suspend fun dismissKeyguard() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ if (proxyConnector.run { it.dismissKeyguard(onDoneBinder) }) {
+ completion.await()
+ } else {
+ Log.wtf(TAG, "Keyguard dismissal request failed")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt
new file mode 100644
index 000000000000..4dd5cc4b55b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.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.screenshot.proxy
+
+import android.app.Service
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.screenshot.ScreenshotProxyService
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SystemUiProxyModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenshotProxyService::class)
+ fun bindScreenshotProxyService(service: ScreenshotProxyService): Service
+
+ @Binds
+ @SysUISingleton
+ fun bindSystemUiProxy(systemUiProxyClient: SystemUiProxyClient): SystemUiProxy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c93ef65ae7c6..e4f5aeb5028c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -334,6 +334,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final ScrimController mScrimController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
+ private final ShadeHeaderController mShadeHeaderController;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -730,6 +731,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
FragmentService fragmentService,
IStatusBarService statusBarService,
ContentResolver contentResolver,
+ ShadeHeaderController shadeHeaderController,
ScreenOffAnimationController screenOffAnimationController,
LockscreenGestureLogger lockscreenGestureLogger,
ShadeExpansionStateManager shadeExpansionStateManager,
@@ -874,6 +876,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mSplitShadeEnabled =
mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
mView.setWillNotDraw(!DEBUG_DRAWABLE);
+ mShadeHeaderController = shadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
@@ -1107,6 +1110,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
mTapAgainViewController.init();
+ mShadeHeaderController.init();
+ mShadeHeaderController.setShadeCollapseAction(
+ () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f));
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -4968,6 +4974,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return false;
}
+ if (DeviceEntryUdfpsRefactor.isEnabled()
+ && mAlternateBouncerInteractor.isVisibleState()) {
+ // never send touches to shade if the alternate bouncer is showing
+ return false;
+ }
+
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getDownTime() == mLastTouchDownTime) {
// An issue can occur when swiping down after unlock, where multiple down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index cde45f2060e5..0de3c10329e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,6 +29,9 @@ interface ShadeInteractor : BaseShadeInteractor {
/** Emits true if the shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean>
+ /** Emits true if QS is currently allowed and false otherwise. */
+ val isQsEnabled: StateFlow<Boolean>
+
/** Whether either the shade or QS is fully expanded. */
val isAnyFullyExpanded: StateFlow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 5fbd2cfaec79..883ef97a6aad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -29,6 +29,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
private val inactiveFlowBoolean = MutableStateFlow(false)
private val inactiveFlowFloat = MutableStateFlow(0f)
override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
+ override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean
override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat
override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 7e14e58b4c9c..d68e28c930f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -60,6 +60,11 @@ constructor(
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+ override val isQsEnabled: StateFlow<Boolean> =
+ disableFlagsRepository.disableFlags
+ .map { it.isQuickSettingsEnabled() }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
override val isAnyFullyExpanded: StateFlow<Boolean> =
anyExpansion
.map { it >= 1f }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 60810a06249f..d8216dcfdec6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -23,8 +23,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeHeaderController
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
@@ -46,25 +44,15 @@ constructor(
private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
private val controller: SplitShadeStateController,
- private val shadeController: ShadeController,
- private val shadeHeaderController: ShadeHeaderController,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
) : CoreStartable {
override fun start() {
hydrateShadeMode()
logTouchesTo(touchLog)
- initHeaderController()
scrimShadeTransitionController.init()
}
- private fun initHeaderController() {
- shadeHeaderController.init()
- shadeHeaderController.shadeCollapseAction = Runnable {
- shadeController.animateCollapseShade()
- }
- }
-
private fun hydrateShadeMode() {
applicationScope.launch {
configurationRepository.onAnyConfigurationChange
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 1191c0f247f9..72a9c8d01554 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.util.Date
@@ -53,6 +54,7 @@ class ShadeHeaderViewModel
constructor(
@Application private val applicationScope: CoroutineScope,
context: Context,
+ shadeInteractor: ShadeInteractor,
mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
@@ -85,6 +87,12 @@ constructor(
/** Whether or not the privacy chip is enabled in the device privacy config. */
val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
+ /** Whether or not the Shade Header should be disabled based on disableFlags. */
+ val isDisabled: StateFlow<Boolean> =
+ shadeInteractor.isQsEnabled
+ .map { !it }
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
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 0fd05550dbe3..c29a64ed61b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -36,6 +36,7 @@ import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
@@ -45,6 +46,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -95,6 +97,7 @@ constructor(
private val deviceProvisionedController: DeviceProvisionedController,
private val bypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
private val dumpManager: DumpManager,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@@ -123,7 +126,7 @@ constructor(
private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList()
// Smartspace can be used on multiple displays, such as when the user casts their screen
- private var smartspaceViews = mutableSetOf<SmartspaceView>()
+ @VisibleForTesting var smartspaceViews = mutableSetOf<SmartspaceView>()
private var regionSamplers =
mutableMapOf<SmartspaceView, RegionSampler>()
@@ -272,6 +275,18 @@ constructor(
}
}
+ // TODO(b/331451011): Refactor to viewmodel and use interactor pattern.
+ private val wakefulnessLifecycleObserver =
+ object : WakefulnessLifecycle.Observer {
+ override fun onStartedWakingUp() {
+ smartspaceViews.forEach { it.setScreenOn(true) }
+ }
+
+ override fun onFinishedGoingToSleep() {
+ smartspaceViews.forEach { it.setScreenOn(false) }
+ }
+ }
+
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
dumpManager.registerDumpable(this)
@@ -451,6 +466,7 @@ constructor(
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
+ wakefulnessLifecycle.addObserver(wakefulnessLifecycleObserver)
datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -493,6 +509,7 @@ constructor(
configurationController.removeCallback(configChangeListener)
statusBarStateController.removeCallback(statusBarStateListener)
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
+ wakefulnessLifecycle.removeObserver(wakefulnessLifecycleObserver)
session = null
datePlugin?.registerSmartspaceEventNotifier(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ad3012e54d75..e93c0f6a5559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -52,6 +52,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
@@ -279,7 +280,9 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
}
}
- mShadeHeaderController.disable(state1, state2, animate);
+ if (!SceneContainerFlag.isEnabled()) {
+ mShadeHeaderController.disable(state1, state2, animate);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9633cb085afa..579d43b5fd05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -453,6 +453,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
setTopMargin(0);
if (grandParent != null) grandParent.setClipChildren(true);
setVisibility(GONE);
+ setAlpha(1f);
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
new file mode 100644
index 000000000000..12f334ba08bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.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 com.android.systemui.accessibility;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.window.InputTransferToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Supplier;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class FullscreenMagnificationControllerTest extends SysuiTestCase {
+
+ private FullscreenMagnificationController mFullscreenMagnificationController;
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+
+ @Before
+ public void setUp() {
+ getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
+ new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification"));
+
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+
+ mFullscreenMagnificationController = new FullscreenMagnificationController(
+ mContext,
+ mContext.getSystemService(AccessibilityManager.class),
+ mContext.getSystemService(WindowManager.class),
+ scvhSupplier);
+ }
+
+ @After
+ public void tearDown() {
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
+ }
+
+ @Test
+ public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true)
+ );
+
+ // Wait for Rects updated.
+ waitForIdleSync();
+ assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
+ }
+
+ @Test
+ public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
+ getInstrumentation().runOnMainSync(
+ () -> {
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true);
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false);
+ }
+ );
+
+ assertThat(mSurfaceControlViewHost.getView()).isNull();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index bd49927c8b53..41d5d5d919e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -71,6 +71,8 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
@Mock
private WindowMagnificationController mWindowMagnificationController;
@Mock
+ private FullscreenMagnificationController mFullscreenMagnificationController;
+ @Mock
private MagnificationSettingsController mMagnificationSettingsController;
@Mock
private ModeSwitchesController mModeSwitchesController;
@@ -105,6 +107,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
mMagnification.mWindowMagnificationControllerSupplier =
new FakeWindowMagnificationControllerSupplier(
mContext.getSystemService(DisplayManager.class));
+ mMagnification.mFullscreenMagnificationControllerSupplier =
+ new FakeFullscreenMagnificationControllerSupplier(
+ mContext.getSystemService(DisplayManager.class));
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class));
@@ -124,6 +129,15 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
}
@Test
+ public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
+ mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
+ waitForIdleSync();
+
+ verify(mFullscreenMagnificationController)
+ .onFullscreenMagnificationActivationChanged(eq(true));
+ }
+
+ @Test
public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
mAnimationCallback);
@@ -215,6 +229,20 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
}
}
+
+ private class FakeFullscreenMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+ FakeFullscreenMagnificationControllerSupplier(DisplayManager displayManager) {
+ super(displayManager);
+ }
+
+ @Override
+ protected FullscreenMagnificationController createInstance(Display display) {
+ return mFullscreenMagnificationController;
+ }
+ }
+
private class FakeSettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 51828c91de4b..6ebda4db808f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,6 +14,7 @@ import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewRootImpl
+import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.Flags
@@ -52,6 +53,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
+ private lateinit var windowManager: WindowManager
+ @Mock
private lateinit var keyguardViewMediator: KeyguardViewMediator
@Mock
private lateinit var keyguardStateController: KeyguardStateController
@@ -99,7 +102,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
+ windowManager, context.resources,
+ keyguardStateController, { keyguardViewMediator }, keyguardViewController,
featureFlags, { biometricUnlockController }, statusBarStateController,
notificationShadeWindowController, powerManager, wallpaperManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 7e41745936f8..0847f01f12d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -30,8 +30,6 @@ import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
@@ -44,35 +42,8 @@ class RequestProcessorTest {
private val component = ComponentName("android.test", "android.test.Component")
private val bounds = Rect(25, 25, 75, 75)
- private val scope = CoroutineScope(Dispatchers.Unconfined)
private val policy = FakeScreenshotPolicy()
- /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
- @Test
- fun testProcessAsync_ScreenshotData() {
- val request =
- ScreenshotData.fromRequest(
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_KEY_OTHER)
- .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build()
- )
- val processor = RequestProcessor(imageCapture, policy, scope)
-
- var result: ScreenshotData? = null
- var callbackCount = 0
- val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData ->
- result = processedRequest
- callbackCount++
- }
-
- // runs synchronously, using Unconfined Dispatcher
- processor.processAsync(request, callback)
-
- // Callback invoked once returning the same request (no changes)
- assertThat(callbackCount).isEqualTo(1)
- assertThat(result).isEqualTo(request)
- }
-
@Test
fun testFullScreenshot() = runBlocking {
// Indicate that the primary content belongs to a normal user
@@ -84,7 +55,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -109,7 +80,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -136,7 +107,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -146,7 +117,7 @@ class RequestProcessorTest {
@Test
fun testProvidedImageScreenshot() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
policy.setManagedProfile(USER_ID, false)
@@ -171,7 +142,7 @@ class RequestProcessorTest {
@Test
fun testProvidedImageScreenshot_managedProfile() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
// Indicate that the screenshot belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
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 0baee5dd0e9d..c900463c7159 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -57,7 +57,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
private val eventLogger = UiEventLoggerFake()
private val screenshotExecutor =
- TakeScreenshotExecutor(
+ TakeScreenshotExecutorImpl(
controllerFactory,
fakeDisplayRepository,
testScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index f3809aad4de3..0776aa7d1845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -20,134 +20,97 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
import android.app.admin.DevicePolicyResourcesManager
import android.content.ComponentName
+import android.net.Uri
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
-import android.view.Display
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import java.util.function.Consumer
+import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doThrow
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
-@SmallTest
class TakeScreenshotServiceTest : SysuiTestCase() {
- private val application = mock<Application>()
- private val controller = mock<ScreenshotController>()
- private val controllerFactory = mock<ScreenshotController.Factory>()
- private val takeScreenshotExecutor = mock<TakeScreenshotExecutor>()
- private val userManager = mock<UserManager>()
- private val requestProcessor = mock<RequestProcessor>()
- private val devicePolicyManager = mock<DevicePolicyManager>()
- private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
- private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
+ private val userManager = mock<UserManager> { on { isUserUnlocked } doReturn (true) }
+
+ private val devicePolicyResourcesManager =
+ mock<DevicePolicyResourcesManager> {
+ on { getString(eq(SCREENSHOT_BLOCKED_BY_ADMIN), /* defaultStringLoader= */ any()) }
+ .doReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+ }
+
+ private val devicePolicyManager =
+ mock<DevicePolicyManager> {
+ on { resources } doReturn (devicePolicyResourcesManager)
+ on { getScreenCaptureDisabled(/* admin= */ isNull(), eq(UserHandle.USER_ALL)) }
+ .doReturn(false)
+ }
+
private val notificationsController = mock<ScreenshotNotificationsController>()
- private val callback = mock<RequestCallback>()
+ private val notificationsControllerFactory =
+ ScreenshotNotificationsController.Factory { notificationsController }
+ private val executor = FakeScreenshotExecutor()
+ private val callback = FakeRequestCallback()
private val eventLogger = UiEventLoggerFake()
- private val flags = FakeFeatureFlags()
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
- private lateinit var service: TakeScreenshotService
-
- @Before
- fun setUp() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, false)
- whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
- whenever(
- devicePolicyManager.getScreenCaptureDisabled(
- /* admin component (null: any admin) */ isNull(),
- eq(UserHandle.USER_ALL)
- )
- )
- .thenReturn(false)
- whenever(userManager.isUserUnlocked).thenReturn(true)
- whenever(controllerFactory.create(any(), any())).thenReturn(controller)
- whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
-
- // Stub request processor as a synchronous no-op for tests with the flag enabled
- doAnswer {
- val request: ScreenshotData = it.getArgument(0) as ScreenshotData
- val consumer: Consumer<ScreenshotData> = it.getArgument(1)
- consumer.accept(request)
- }
- .whenever(requestProcessor)
- .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
-
- service = createService()
- }
-
@Test
fun testServiceLifecycle() {
+ val service = createService()
service.onCreate()
service.onBind(null /* unused: Intent */)
+ assertThat(executor.windowsPresent).isTrue()
service.onUnbind(null /* unused: Intent */)
- verify(controller, times(1)).removeWindow()
+ assertThat(executor.windowsPresent).isFalse()
service.onDestroy()
- verify(controller, times(1)).onDestroy()
+ assertThat(executor.destroyed).isTrue()
}
@Test
fun takeScreenshotFullscreen() {
+ val service = createService()
+
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
service.handleRequest(request, { /* onSaved */}, callback)
+ assertWithMessage("request received by executor").that(executor.requestReceived).isNotNull()
- verify(controller, times(1))
- .handleScreenshot(
- eq(ScreenshotData.fromRequest(request, Display.DEFAULT_DISPLAY)),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any()
- )
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals(
- "Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId,
- SCREENSHOT_REQUESTED_KEY_OTHER.id
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- eventLogger.get(0).packageName
- )
+ assertWithMessage("request received by executor")
+ .that(ScreenshotData.fromRequest(executor.requestReceived!!))
+ .isEqualTo(ScreenshotData.fromRequest(request))
}
@Test
fun takeScreenshotFullscreen_userLocked() {
- whenever(userManager.isUserUnlocked).thenReturn(false)
+ val service = createService()
+ whenever(userManager.isUserUnlocked).doReturn(false)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
@@ -157,100 +120,51 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
service.handleRequest(request, { /* onSaved */}, callback)
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ assertWithMessage("callback errorReported").that(callback.errorReported).isTrue()
+
+ assertWithMessage("UiEvent count").that(eventLogger.numLogs()).isEqualTo(2)
+
val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
+ assertWithMessage("request UiEvent id")
+ .that(requestEvent.eventId)
+ .isEqualTo(SCREENSHOT_REQUESTED_KEY_OTHER.id)
+
+ assertWithMessage("topComponent package name")
+ .that(requestEvent.packageName)
+ .isEqualTo(topComponent.packageName)
+
val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
+ assertWithMessage("failure UiEvent id")
+ .that(failureEvent.eventId)
+ .isEqualTo(SCREENSHOT_CAPTURE_FAILED.id)
+
+ assertWithMessage("Supplied package name")
+ .that(failureEvent.packageName)
+ .isEqualTo(topComponent.packageName)
}
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
- .thenReturn(true)
+ val service = createService()
whenever(
- devicePolicyResourcesManager.getString(
- eq(SCREENSHOT_BLOCKED_BY_ADMIN),
- /* Supplier<String> */
- any(),
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin= */ isNull(),
+ eq(UserHandle.USER_ALL)
)
)
- .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- // error shown: Toast.makeText(...).show(), untestable
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
- val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
- val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
- }
+ .doReturn(true)
- @Test
- fun takeScreenshot_workProfile_nullBitmap() {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
- doThrow(IllegalStateException::class.java)
- .whenever(requestProcessor)
- .processAsync(any(ScreenshotData::class.java), any())
-
service.handleRequest(request, { /* onSaved */}, callback)
+ assertThat(callback.errorReported).isTrue()
+ assertWithMessage("Expected two UiEvents").that(eventLogger.numLogs()).isEqualTo(2)
- verify(callback, times(1)).reportError()
- verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
val requestEvent = eventLogger.get(0)
assertEquals(
"Expected SCREENSHOT_REQUESTED_* UiEvent",
@@ -275,71 +189,70 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
)
}
- @Test
- fun takeScreenshotFullScreen_multiDisplayFlagEnabled_takeScreenshotExecutor() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- service = createService()
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verifyZeroInteractions(controller)
- verify(takeScreenshotExecutor, times(1)).executeScreenshotsAsync(any(), any(), any())
-
- assertEquals("Expected one UiEvent", 0, eventLogger.numLogs())
- }
-
- @Test
- fun testServiceLifecycle_multiDisplayScreenshotFlagEnabled() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- service = createService()
-
- service.onCreate()
- service.onBind(null /* unused: Intent */)
-
- service.onUnbind(null /* unused: Intent */)
- verify(takeScreenshotExecutor, times(1)).removeWindows()
-
- service.onDestroy()
- verify(takeScreenshotExecutor, times(1)).onDestroy()
- }
-
- @Test
- fun constructor_MultiDisplayFlagOn_screenshotControllerNotCreated() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- clearInvocations(controllerFactory)
-
- service = createService()
-
- verifyZeroInteractions(controllerFactory)
- }
-
private fun createService(): TakeScreenshotService {
val service =
TakeScreenshotService(
- controllerFactory,
userManager,
devicePolicyManager,
eventLogger,
notificationsControllerFactory,
mContext,
Runnable::run,
- flags,
- requestProcessor,
- { takeScreenshotExecutor },
+ executor
)
+
service.attach(
mContext,
/* thread = */ null,
/* className = */ null,
/* token = */ null,
- application,
+ mock<Application>(),
/* activityManager = */ null
)
return service
}
}
+
+internal class FakeRequestCallback : RequestCallback {
+ var errorReported = false
+ var finished = false
+ override fun reportError() {
+ errorReported = true
+ }
+
+ override fun onFinish() {
+ finished = true
+ }
+}
+
+internal class FakeScreenshotExecutor : TakeScreenshotExecutor {
+ var requestReceived: ScreenshotRequest? = null
+ var windowsPresent = true
+ var destroyed = false
+ override fun onCloseSystemDialogsReceived() {}
+ override suspend fun executeScreenshots(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: (Uri) -> Unit,
+ requestCallback: RequestCallback,
+ ) {
+ requestReceived = screenshotRequest
+ }
+
+ override fun removeWindows() {
+ windowsPresent = false
+ }
+
+ override fun onDestroy() {
+ destroyed = true
+ }
+
+ override fun executeScreenshotsAsync(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: Consumer<Uri>,
+ requestCallback: RequestCallback,
+ ) {
+ runBlocking {
+ executeScreenshots(screenshotRequest, { onSaved.accept(it) }, requestCallback)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 56e61e4c6442..e957ca2eca3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -711,6 +711,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFragmentService,
mStatusBarService,
mContentResolver,
+ mShadeHeaderController,
mScreenOffAnimationController,
mLockscreenGestureLogger,
mShadeExpansionStateManager,
@@ -896,8 +897,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mConfigurationController.onConfigurationChanged(configuration);
}
- protected void onTouchEvent(MotionEvent ev) {
- mTouchHandler.onTouch(mView, ev);
+ protected boolean onTouchEvent(MotionEvent ev) {
+ return mTouchHandler.onTouch(mView, ev);
}
protected void setDozing(boolean dozing, boolean dozingAlwaysOn) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 6d5d5be8ae57..29a92d969ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -364,6 +364,24 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void alternateBouncerVisible_onTouchEvent_notHandled() {
+ mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ // GIVEN alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // WHEN touch DOWN event received; THEN touch is NOT handled
+ assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */))).isFalse();
+
+ // WHEN touch MOVE event received; THEN touch is NOT handled
+ assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+ 0 /* metaState */))).isFalse();
+
+ }
+
+ @Test
public void test_onTouchEvent_startTracking() {
// GIVEN device is NOT pulsing
mNotificationPanelViewController.setPulsing(false);
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 a5f3f57f5be6..5abad61d90ad 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
@@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -180,6 +181,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private lateinit var dateSmartspaceView: SmartspaceView
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView
+ private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@@ -225,6 +227,14 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
setAllowPrivateNotifications(userHandleSecondary, true)
setShowNotifications(userHandlePrimary, true)
+ // Use the real wakefulness lifecycle instead of a mock
+ wakefulnessLifecycle = WakefulnessLifecycle(
+ context,
+ /* wallpaper= */ null,
+ clock,
+ dumpManager
+ )
+
controller = LockscreenSmartspaceController(
context,
featureFlags,
@@ -240,6 +250,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
deviceProvisionedController,
keyguardBypassController,
keyguardUpdateMonitor,
+ wakefulnessLifecycle,
dumpManager,
execution,
executor,
@@ -773,6 +784,38 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(configurationController, never()).addCallback(any())
}
+ @Test
+ fun testWakefulnessLifecycleDispatch_wake_setsSmartspaceScreenOnTrue() {
+ // Connect session
+ connectSession()
+
+ // Add mock views
+ val mockSmartspaceView = mock(SmartspaceView::class.java)
+ controller.smartspaceViews.add(mockSmartspaceView)
+
+ // Initiate wakefulness change
+ wakefulnessLifecycle.dispatchStartedWakingUp(0)
+
+ // Verify smartspace views receive screen on
+ verify(mockSmartspaceView).setScreenOn(true)
+ }
+
+ @Test
+ fun testWakefulnessLifecycleDispatch_sleep_setsSmartspaceScreenOnFalse() {
+ // Connect session
+ connectSession()
+
+ // Add mock views
+ val mockSmartspaceView = mock(SmartspaceView::class.java)
+ controller.smartspaceViews.add(mockSmartspaceView)
+
+ // Initiate wakefulness change
+ wakefulnessLifecycle.dispatchFinishedGoingToSleep()
+
+ // Verify smartspace views receive screen on
+ verify(mockSmartspaceView).setScreenOn(false)
+ }
+
private fun connectSession() {
val dateView = controller.buildAndConnectDateView(fakeParent)
dateSmartspaceView = dateView as SmartspaceView
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index c25978271b17..70afbd82df11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -67,10 +67,10 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -448,6 +448,34 @@ public class RemoteInputViewTest extends SysuiTestCase {
assertEquals(1f, fadeInView.getAlpha());
}
+ @Test
+ public void testUnanimatedFocusAfterDefocusAnimation() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ bindController(view, row.getEntry());
+
+ FrameLayout parent = new FrameLayout(mContext);
+ parent.addView(view);
+
+ // Play defocus animation
+ view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */);
+ mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+ // assert that RemoteInputView is no longer visible, but alpha is reset to 1f
+ assertEquals(View.GONE, view.getVisibility());
+ assertEquals(1f, view.getAlpha());
+
+ // focus RemoteInputView without an animation
+ view.focus();
+ // assert that RemoteInputView is visible, and alpha is 1f
+ assertEquals(View.VISIBLE, view.getVisibility());
+ assertEquals(1f, view.getAlpha());
+ }
+
// NOTE: because we're refactoring the RemoteInputView and moving logic into the
// RemoteInputViewController, it's easiest to just test the system of the two classes together.
@NonNull
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index 65e04f4e476d..7dfa68605ef3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -22,9 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.LogBuffer
-import com.android.systemui.shade.ShadeHeaderController
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shadeController
import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.splitShadeStateController
import com.android.systemui.util.mockito.mock
@@ -37,8 +35,6 @@ val Kosmos.shadeStartable by Fixture {
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
controller = splitShadeStateController,
- shadeHeaderController = mock<ShadeHeaderController>(),
scrimShadeTransitionController = mock<ScrimShadeTransitionController>(),
- shadeController = shadeController
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index fb2805574ff0..1f65e15c1bff 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -37,6 +37,8 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -695,6 +697,13 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
throw new IllegalArgumentException(
bluetoothAddress + " is not a valid Bluetooth address");
}
+ final BluetoothManager bluetoothManager =
+ mContext.getSystemService(BluetoothManager.class);
+ final String bluetoothDeviceName = bluetoothManager == null ? null :
+ bluetoothManager.getAdapter().getBondedDevices().stream()
+ .filter(device -> device.getAddress().equalsIgnoreCase(bluetoothAddress))
+ .map(BluetoothDevice::getName)
+ .findFirst().orElse(null);
synchronized (mLock) {
checkAccessibilityAccessLocked();
if (mBrailleDisplayConnection != null) {
@@ -706,7 +715,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
connection.setTestData(mTestBrailleDisplays);
}
connection.connectLocked(
- bluetoothAddress, BrailleDisplayConnection.BUS_BLUETOOTH, controller);
+ bluetoothAddress,
+ bluetoothDeviceName,
+ BrailleDisplayConnection.BUS_BLUETOOTH,
+ controller);
}
}
@@ -763,7 +775,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
connection.setTestData(mTestBrailleDisplays);
}
connection.connectLocked(
- usbSerialNumber, BrailleDisplayConnection.BUS_USB, controller);
+ usbSerialNumber,
+ usbDevice.getProductName(),
+ BrailleDisplayConnection.BUS_USB,
+ controller);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
index 8b41873636a9..b0da3f014452 100644
--- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
@@ -24,6 +24,7 @@ import android.accessibilityservice.IBrailleDisplayConnection;
import android.accessibilityservice.IBrailleDisplayController;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.PermissionManuallyEnforced;
import android.annotation.RequiresNoPermission;
import android.bluetooth.BluetoothDevice;
@@ -33,6 +34,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -141,6 +143,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
@BusType
int getDeviceBusType(@NonNull Path path);
+
+ String getName(@NonNull Path path);
}
/**
@@ -149,15 +153,19 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
* <p>If found, saves instance state for this connection and starts a thread to
* read from the Braille display.
*
- * @param expectedUniqueId The expected unique ID of the device to connect, from
- * {@link UsbDevice#getSerialNumber()}
- * or {@link BluetoothDevice#getAddress()}
- * @param expectedBusType The expected bus type from {@link BusType}.
- * @param controller Interface containing oneway callbacks used to communicate with the
- * {@link android.accessibilityservice.BrailleDisplayController}.
+ * @param expectedUniqueId The expected unique ID of the device to connect, from
+ * {@link UsbDevice#getSerialNumber()} or
+ * {@link BluetoothDevice#getAddress()}.
+ * @param expectedName The expected name of the device to connect, from
+ * {@link BluetoothDevice#getName()} or
+ * {@link UsbDevice#getProductName()}.
+ * @param expectedBusType The expected bus type from {@link BusType}.
+ * @param controller Interface containing oneway callbacks used to communicate with the
+ * {@link android.accessibilityservice.BrailleDisplayController}.
*/
void connectLocked(
@NonNull String expectedUniqueId,
+ @Nullable String expectedName,
@BusType int expectedBusType,
@NonNull IBrailleDisplayController controller) {
Objects.requireNonNull(expectedUniqueId);
@@ -179,10 +187,20 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
unableToGetDescriptor = true;
continue;
}
+ final boolean matchesIdentifier;
final String uniqueId = mScanner.getUniqueId(path);
+ if (uniqueId != null) {
+ matchesIdentifier = expectedUniqueId.equalsIgnoreCase(uniqueId);
+ } else {
+ // HIDIOCGRAWUNIQ was added in kernel version 5.7.
+ // If the device has an older kernel that does not support that ioctl then as a
+ // fallback we can check against the device name (from HIDIOCGRAWNAME).
+ final String name = mScanner.getName(path);
+ matchesIdentifier = !TextUtils.isEmpty(expectedName) && expectedName.equals(name);
+ }
if (isBrailleDisplay(descriptor)
&& mScanner.getDeviceBusType(path) == expectedBusType
- && expectedUniqueId.equalsIgnoreCase(uniqueId)) {
+ && matchesIdentifier) {
result.add(Pair.create(path.toFile(), descriptor));
}
}
@@ -498,6 +516,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
Integer busType = readFromFileDescriptor(path, nativeInterface::getHidrawBusType);
return busType != null ? busType : BUS_UNKNOWN;
}
+
+ @Override
+ public String getName(@NonNull Path path) {
+ Objects.requireNonNull(path);
+ return readFromFileDescriptor(path, nativeInterface::getHidrawName);
+ }
};
}
@@ -542,6 +566,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH)
? BUS_BLUETOOTH : BUS_USB;
}
+
+ @Override
+ public String getName(@NonNull Path path) {
+ return brailleDisplayMap.get(path).getString(
+ BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME);
+ }
};
return mScanner;
}
@@ -579,6 +609,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
* @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails.
*/
int getHidrawBusType(int fd);
+
+ String getHidrawName(int fd);
}
/** Native interface that actually calls native HIDRAW ioctls. */
@@ -602,6 +634,11 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
public int getHidrawBusType(int fd) {
return nativeGetHidrawBusType(fd);
}
+
+ @Override
+ public String getHidrawName(int fd) {
+ return nativeGetHidrawName(fd);
+ }
}
private static native int nativeGetHidrawDescSize(int fd);
@@ -611,4 +648,6 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
private static native String nativeGetHidrawUniq(int fd);
private static native int nativeGetHidrawBusType(int fd);
+
+ private static native String nativeGetHidrawName(int fd);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 993a1d5494a2..558e07f77656 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -53,8 +53,10 @@ import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_RETRIGGER;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT;
import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER;
@@ -543,6 +545,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
synchronized (mLock) {
int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class);
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_RETRIGGER);
mAssistReceiver.processDelayedFillLocked(requestId, response);
}
}
@@ -1268,7 +1272,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if(mPreviouslyFillDialogPotentiallyStarted) {
mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER);
} else {
- mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER);
+ if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_EXPLICITLY_REQUESTED);
+ } else {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_NORMAL_TRIGGER);
+ }
}
if (existingResponse != null) {
setViewStatesLocked(
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index e3f16ae07202..253fe35a3e29 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -20,6 +20,11 @@ import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -86,9 +91,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private static class MediaProjectionSession {
final int mUid;
final long mSessionId;
+ final boolean mIsExempted;
- MediaProjectionSession(int uid, long sessionId) {
+ MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
mUid = uid;
+ mIsExempted = isExempted;
mSessionId = sessionId;
}
}
@@ -105,11 +112,28 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mMediaProjectionSession.mSessionId,
+ mMediaProjectionSession.mUid,
+ mMediaProjectionSession.mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mMediaProjectionSession.mSessionId,
+ mMediaProjectionSession.mUid,
+ mMediaProjectionSession.mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStop");
try {
@@ -207,28 +231,25 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
private void onProjectionStart(MediaProjectionInfo projectionInfo) {
- // exempt on device screen recorder as well.
- if ((mExemptedPackages != null && mExemptedPackages.contains(
+ int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
+ projectionInfo.getUserHandle().getIdentifier());
+ boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains(
projectionInfo.getPackageName()))
- || canRecordSensitiveContent(projectionInfo.getPackageName())) {
- Log.w(TAG, projectionInfo.getPackageName() + " is exempted.");
- return;
- }
+ || canRecordSensitiveContent(projectionInfo.getPackageName());
// TODO(b/324447419): move GlobalSettings lookup to background thread
- boolean disableScreenShareProtections =
- Settings.Global.getInt(getContext().getContentResolver(),
- DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
- if (disableScreenShareProtections) {
- Log.w(TAG, "Screen share protections disabled, ignoring projection start");
+ boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+ mMediaProjectionSession = new MediaProjectionSession(
+ uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+
+ if (isPackageExempted || isFeatureDisabled) {
+ Log.w(TAG, "projection session is exempted, package ="
+ + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
return;
}
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = true;
- int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
- projectionInfo.getUserHandle().getIdentifier());
- // TODO review sessionId, whether to use a sequence generator or random is good?
- mMediaProjectionSession = new MediaProjectionSession(uid, new Random().nextLong());
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index c18bacb51671..72a55dbea481 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -177,6 +177,7 @@ public class Watchdog implements Dumpable {
"android.hardware.biometrics.fingerprint.IFingerprint/",
"android.hardware.bluetooth.IBluetoothHci/",
"android.hardware.camera.provider.ICameraProvider/",
+ "android.hardware.drm.IDrmFactory/",
"android.hardware.gnss.IGnss/",
"android.hardware.graphics.allocator.IAllocator/",
"android.hardware.graphics.composer3.IComposer/",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c020a9ff1c27..c2613bc47eb2 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -993,6 +993,13 @@ class UserController implements Handler.Callback {
if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
+ // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
+ final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+ if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
+ }
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
int[] usersToStop = getUsersToStopLU(userId);
// If one of related users is system or current, no related users should be stopped
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f1eea728bedd..951f676dd098 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -127,8 +127,7 @@ public class AudioDeviceBroker {
private final Object mDeviceStateLock = new Object();
// Request to override default use of A2DP for media.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothA2dpEnabled;
+ private AtomicBoolean mBluetoothA2dpEnabled = new AtomicBoolean(false);
// lock always taken when accessing AudioService.mSetModeDeathHandlers
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
@@ -275,17 +274,8 @@ public class AudioDeviceBroker {
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
- synchronized (mDeviceStateLock) {
- if (mBluetoothA2dpEnabled == on) {
- return;
- }
- mBluetoothA2dpEnabled = on;
- mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
- sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- source);
- }
+ mBluetoothA2dpEnabled.set(on);
+ sendLMsgNoDelay(MSG_L_SET_FORCE_BT_A2DP_USE, SENDMSG_REPLACE, source);
}
/**
@@ -1223,16 +1213,8 @@ public class AudioDeviceBroker {
}
}
- /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
- synchronized (mDeviceStateLock) {
- return mBtHelper.isAvrcpAbsoluteVolumeSupported();
- }
- }
-
/*package*/ boolean isBluetoothA2dpOn() {
- synchronized (mDeviceStateLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mBluetoothA2dpEnabled.get();
}
/*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
@@ -1601,15 +1583,12 @@ public class AudioDeviceBroker {
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).append(" src:").append(source).toString();
- synchronized (mDeviceStateLock) {
- mBluetoothA2dpEnabled = on;
- mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
- onSetForceUse(
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- fromA2dp,
- eventSource);
- }
+ mBluetoothA2dpEnabled.set(on);
+ onSetForceUse(
+ AudioSystem.FOR_MEDIA,
+ on ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ fromA2dp,
+ eventSource);
}
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
@@ -1658,9 +1637,7 @@ public class AudioDeviceBroker {
}
/*package*/ boolean getBluetoothA2dpEnabled() {
- synchronized (mDeviceStateLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mBluetoothA2dpEnabled.get();
}
/*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
@@ -1821,9 +1798,12 @@ public class AudioDeviceBroker {
}
break;
case MSG_IIL_SET_FORCE_USE: // intended fall-through
- case MSG_IIL_SET_FORCE_BT_A2DP_USE:
- onSetForceUse(msg.arg1, msg.arg2,
- (msg.what == MSG_IIL_SET_FORCE_BT_A2DP_USE), (String) msg.obj);
+ onSetForceUse(msg.arg1, msg.arg2, false, (String) msg.obj);
+ break;
+ case MSG_L_SET_FORCE_BT_A2DP_USE:
+ int forcedUsage = mBluetoothA2dpEnabled.get()
+ ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+ onSetForceUse(AudioSystem.FOR_MEDIA, forcedUsage, true, (String) msg.obj);
break;
case MSG_REPORT_NEW_ROUTES:
case MSG_REPORT_NEW_ROUTES_A2DP:
@@ -1831,35 +1811,38 @@ public class AudioDeviceBroker {
mDeviceInventory.onReportNewRoutes();
}
break;
- case MSG_L_SET_BT_ACTIVE_DEVICE:
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
- && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) {
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
- + "received with null profile proxy: "
- + btInfo)).printLog(TAG));
- } else {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_SET_BT_ACTIVE_DEVICE");
+ case MSG_L_SET_BT_ACTIVE_DEVICE: {
+ final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) {
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+ "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
+ + "received with null profile proxy: "
+ + btInfo)).printLog(TAG));
+ } else {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_SET_BT_ACTIVE_DEVICE");
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
(btInfo.mProfile
- != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ != BluetoothProfile.LE_AUDIO
+ || btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
- || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ || btInfo.mProfile
+ == BluetoothProfile.HEARING_AID) {
+ onUpdateCommunicationRouteClient(
+ isBluetoothScoRequested(),
"setBluetoothActiveDevice");
}
}
}
}
- break;
+ } break;
case MSG_BT_HEADSET_CNCT_FAILED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1883,11 +1866,11 @@ public class AudioDeviceBroker {
break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
mDeviceInventory.onBluetoothDeviceConfigChange(
btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2098,7 +2081,7 @@ public class AudioDeviceBroker {
private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
private static final int MSG_IIL_SET_FORCE_USE = 4;
- private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+ private static final int MSG_L_SET_FORCE_BT_A2DP_USE = 5;
private static final int MSG_TOGGLE_HDMI = 6;
private static final int MSG_L_SET_BT_ACTIVE_DEVICE = 7;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
@@ -2295,7 +2278,7 @@ public class AudioDeviceBroker {
MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
- MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ MESSAGES_MUTE_MUSIC.add(MSG_L_SET_FORCE_BT_A2DP_USE);
}
private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 649b9efc3282..be47f8581db3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -46,6 +46,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
+import static com.android.media.audio.Flags.setStreamVolumeOrder;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -4538,6 +4539,8 @@ public class AudioService extends IAudioService.Stub
+ focusFreezeTestApi());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
+ pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
+ + setStreamVolumeOrder());
pw.println("\tandroid.media.audio.foregroundAudioControl:"
+ foregroundAudioControl());
}
@@ -4705,6 +4708,30 @@ public class AudioService extends IAudioService.Stub
index = rescaleIndex(index * 10, streamType, streamTypeAlias);
+ if (setStreamVolumeOrder()) {
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (index < 0) {
+ index = streamState.getMaxIndex();
+ }
+ }
+ }
+
+ if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+ flags)) {
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+ // ada is non-null when called from setDeviceVolume,
+ // which shouldn't update the mute state
+ canChangeMuteAndUpdateController /*canChangeMute*/);
+ index = mStreamStates[streamType].getIndex(device);
+ }
+ }
+
if (streamTypeAlias == AudioSystem.STREAM_MUSIC
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
@@ -4738,26 +4765,28 @@ public class AudioService extends IAudioService.Stub
mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
}
- flags &= ~AudioManager.FLAG_FIXED_VOLUME;
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
- flags |= AudioManager.FLAG_FIXED_VOLUME;
+ if (!setStreamVolumeOrder()) {
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
- // volume is either 0 or max allowed for fixed volume devices
- if (index != 0) {
- index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
- if (index < 0) {
- index = streamState.getMaxIndex();
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (index < 0) {
+ index = streamState.getMaxIndex();
+ }
}
}
- }
- if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
- flags)) {
- onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
- // ada is non-null when called from setDeviceVolume,
- // which shouldn't update the mute state
- canChangeMuteAndUpdateController /*canChangeMute*/);
- index = mStreamStates[streamType].getIndex(device);
+ if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+ flags)) {
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+ // ada is non-null when called from setDeviceVolume,
+ // which shouldn't update the mute state
+ canChangeMuteAndUpdateController /*canChangeMute*/);
+ index = mStreamStates[streamType].getIndex(device);
+ }
}
synchronized (mHdmiClientLock) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0f3f8073edcc..f3a5fdb3cacc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -235,10 +235,6 @@ public class BtHelper {
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
}
- /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() {
- return (mA2dp != null && mAvrcpAbsVolSupported);
- }
-
/*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) {
mAvrcpAbsVolSupported = supported;
Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported);
@@ -648,8 +644,6 @@ public class BtHelper {
}
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized boolean isProfilePoxyConnected(int profile) {
switch (profile) {
case BluetoothProfile.HEADSET:
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index e2ae3def0b1b..e8394d43f266 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -399,7 +399,7 @@ public final class DeviceStateManagerService extends SystemService {
final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE);
final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE);
- return new DeviceStateInfo(supportedStates, baseState,
+ return new DeviceStateInfo(new ArrayList<>(supportedStates), baseState,
createMergedDeviceState(currentState, baseState));
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1546010717c5..4aab9d26dbcb 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -29,6 +29,9 @@ import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -37,19 +40,20 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
import android.util.EventLog;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.Clock;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import java.io.PrintWriter;
@@ -64,6 +68,8 @@ import java.lang.annotation.RetentionPolicy;
public class AutomaticBrightnessController {
private static final String TAG = "AutomaticBrightnessController";
+ private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+
public static final int AUTO_BRIGHTNESS_ENABLED = 1;
public static final int AUTO_BRIGHTNESS_DISABLED = 2;
public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3;
@@ -81,20 +87,32 @@ public class AutomaticBrightnessController {
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
+ // How long the current sensor reading is assumed to be valid beyond the current time.
+ // This provides a bit of prediction, as well as ensures that the weight for the last sample is
+ // non-zero, which in turn ensures that the total weight is non-zero.
+ private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
+
// Debounce for sampling user-initiated changes in display brightness to ensure
// the user is satisfied with the result before storing the sample.
private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
- private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 1;
- private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 2;
- private static final int MSG_UPDATE_FOREGROUND_APP = 3;
- private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 4;
- private static final int MSG_RUN_UPDATE = 5;
- private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 6;
+ private static final int MSG_UPDATE_AMBIENT_LUX = 1;
+ private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
+ private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3;
+ private static final int MSG_UPDATE_FOREGROUND_APP = 4;
+ private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
+ private static final int MSG_RUN_UPDATE = 6;
+ private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7;
// Callbacks for requesting updates to the display's power state
private final Callbacks mCallbacks;
+ // The sensor manager.
+ private final SensorManager mSensorManager;
+
+ // The light sensor, or null if not available or needed.
+ private final Sensor mLightSensor;
+
// The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
@NonNull
private BrightnessMappingStrategy mCurrentBrightnessMapper;
@@ -109,21 +127,94 @@ public class AutomaticBrightnessController {
// How much to scale doze brightness by (should be (0, 1.0]).
private final float mDozeScaleFactor;
+ // Initial light sensor event rate in milliseconds.
+ private final int mInitialLightSensorRate;
+
+ // Steady-state light sensor event rate in milliseconds.
+ private final int mNormalLightSensorRate;
+
+ // The current light sensor event rate in milliseconds.
+ private int mCurrentLightSensorRate;
+
+ // Stability requirements in milliseconds for accepting a new brightness level. This is used
+ // for debouncing the light sensor. Different constants are used to debounce the light sensor
+ // when adapting to brighter or darker environments. This parameter controls how quickly
+ // brightness changes occur in response to an observed change in light level that exceeds the
+ // hysteresis threshold.
+ private final long mBrighteningLightDebounceConfig;
+ private final long mDarkeningLightDebounceConfig;
+ private final long mBrighteningLightDebounceConfigIdle;
+ private final long mDarkeningLightDebounceConfigIdle;
+
+ // If true immediately after the screen is turned on the controller will try to adjust the
+ // brightness based on the current sensor reads. If false, the controller will collect more data
+ // and only then decide whether to change brightness.
+ private final boolean mResetAmbientLuxAfterWarmUpConfig;
+
+ // Period of time in which to consider light samples for a short/long-term estimate of ambient
+ // light in milliseconds.
+ private final int mAmbientLightHorizonLong;
+ private final int mAmbientLightHorizonShort;
+
+ // The intercept used for the weighting calculation. This is used in order to keep all possible
+ // weighting values positive.
+ private final int mWeightingIntercept;
+
// Configuration object for determining thresholds to change brightness dynamically
+ private final HysteresisLevels mAmbientBrightnessThresholds;
private final HysteresisLevels mScreenBrightnessThresholds;
+ private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
private final HysteresisLevels mScreenBrightnessThresholdsIdle;
private boolean mLoggingEnabled;
+
+ // Amount of time to delay auto-brightness after screen on while waiting for
+ // the light sensor to warm-up in milliseconds.
+ // May be 0 if no warm-up is required.
+ private int mLightSensorWarmUpTimeConfig;
+
+ // Set to true if the light sensor is enabled.
+ private boolean mLightSensorEnabled;
+
+ // The time when the light sensor was enabled.
+ private long mLightSensorEnableTime;
+
// The currently accepted nominal ambient light level.
private float mAmbientLux;
+
+ // The last calculated ambient light level (long time window).
+ private float mSlowAmbientLux;
+
+ // The last calculated ambient light level (short time window).
+ private float mFastAmbientLux;
+
+ // The last ambient lux value prior to passing the darkening or brightening threshold.
+ private float mPreThresholdLux;
+
// True if mAmbientLux holds a valid value.
private boolean mAmbientLuxValid;
+
+ // The ambient light level threshold at which to brighten or darken the screen.
+ private float mAmbientBrighteningThreshold;
+ private float mAmbientDarkeningThreshold;
+
// The last brightness value prior to passing the darkening or brightening threshold.
private float mPreThresholdBrightness;
// The screen brightness threshold at which to brighten or darken the screen.
private float mScreenBrighteningThreshold;
private float mScreenDarkeningThreshold;
+ // The most recent light sample.
+ private float mLastObservedLux = INVALID_LUX;
+
+ // The time of the most light recent sample.
+ private long mLastObservedLuxTime;
+
+ // The number of light samples collected since the light sensor was enabled.
+ private int mRecentLightSamples;
+
+ // A ring buffer containing all of the recent ambient light sensor readings.
+ private AmbientLightRingBuffer mAmbientLightRingBuffer;
// The handler
private AutomaticBrightnessHandler mHandler;
@@ -182,55 +273,88 @@ public class AutomaticBrightnessController {
private Context mContext;
private int mState = AUTO_BRIGHTNESS_DISABLED;
- private final Clock mClock;
+ private Clock mClock;
private final Injector mInjector;
- private final LightSensorController mLightSensorController;
-
- AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager,
+ AutomaticBrightnessController(Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
- this(new Injector(), callbacks, looper,
- brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor,
- screenBrightnessThresholds,
+ this(new Injector(), callbacks, looper, sensorManager, lightSensor,
+ brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
+ dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
- brightnessThrottler, userLux, userNits,
- new LightSensorController(sensorManager, looper, displayId, config),
- brightnessClamperController
+ brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+ userNits, brightnessClamperController
);
}
@VisibleForTesting
AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessRangeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- LightSensorController lightSensorController,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
mInjector = injector;
mClock = injector.createClock();
mContext = context;
mCallbacks = callbacks;
+ mSensorManager = sensorManager;
mCurrentBrightnessMapper = brightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT);
mScreenBrightnessRangeMinimum = brightnessMin;
mScreenBrightnessRangeMaximum = brightnessMax;
+ mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
mDozeScaleFactor = dozeScaleFactor;
+ mNormalLightSensorRate = lightSensorRate;
+ mInitialLightSensorRate = initialLightSensorRate;
+ mCurrentLightSensorRate = -1;
+ mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
+ mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
+ mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle;
+ mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle;
+ mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
+ mAmbientLightHorizonLong = ambientLightHorizonLong;
+ mAmbientLightHorizonShort = ambientLightHorizonShort;
+ mWeightingIntercept = ambientLightHorizonLong;
+ mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+ mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
mScreenBrightnessThresholds = screenBrightnessThresholds;
mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
mShortTermModel = new ShortTermModel();
mPausedShortTermModel = new ShortTermModel();
mHandler = new AutomaticBrightnessHandler(looper);
- mLightSensorController = lightSensorController;
- mLightSensorController.setListener(this::setAmbientLux);
+ mAmbientLightRingBuffer =
+ new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock);
+
+ if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
+ mLightSensor = lightSensor;
+ }
+
mActivityTaskManager = ActivityTaskManager.getService();
mPackageManager = mContext.getPackageManager();
mTaskStackListener = new TaskStackListenerImpl();
@@ -282,13 +406,13 @@ public class AutomaticBrightnessController {
if (brightnessEvent != null) {
brightnessEvent.setLux(
mAmbientLuxValid ? mAmbientLux : PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ brightnessEvent.setPreThresholdLux(mPreThresholdLux);
brightnessEvent.setPreThresholdBrightness(mPreThresholdBrightness);
brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
- mLightSensorController.updateBrightnessEvent(brightnessEvent);
}
if (!mAmbientLuxValid) {
@@ -311,22 +435,21 @@ public class AutomaticBrightnessController {
*/
public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
BrightnessEvent brightnessEvent) {
- float lastObservedLux = mLightSensorController.getLastObservedLux();
- if (lastObservedLux == INVALID_LUX) {
+ if (mLastObservedLux == INVALID_LUX) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- float brightness = mCurrentBrightnessMapper.getBrightness(lastObservedLux,
+ float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
mForegroundAppPackageName, mForegroundAppCategory);
if (shouldApplyDozeScaleFactor()) {
brightness *= mDozeScaleFactor;
}
if (brightnessEvent != null) {
- brightnessEvent.setLux(lastObservedLux);
+ brightnessEvent.setLux(mLastObservedLux);
brightnessEvent.setRecommendedBrightness(brightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
- | (lastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
+ | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
@@ -378,7 +501,6 @@ public class AutomaticBrightnessController {
public void stop() {
setLightSensorEnabled(false);
- mLightSensorController.stop();
}
public boolean hasUserDataPoints() {
@@ -407,6 +529,14 @@ public class AutomaticBrightnessController {
return mAmbientLux;
}
+ float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
private boolean setDisplayPolicy(int policy) {
if (mDisplayPolicy == policy) {
return false;
@@ -485,13 +615,36 @@ public class AutomaticBrightnessController {
pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
pw.println(" mDozeScaleFactor=" + mDozeScaleFactor);
+ pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate);
+ pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate);
+ pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+ pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
+ pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
+ pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
+ pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
+ pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
+ pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
+ pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
+ pw.println(" mWeightingIntercept=" + mWeightingIntercept);
+
pw.println();
pw.println("Automatic Brightness Controller State:");
+ pw.println(" mLightSensor=" + mLightSensor);
+ pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
+ pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
+ pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate);
pw.println(" mAmbientLux=" + mAmbientLux);
pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
+ pw.println(" mPreThresholdLux=" + mPreThresholdLux);
pw.println(" mPreThresholdBrightness=" + mPreThresholdBrightness);
+ pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
+ pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
pw.println(" mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
pw.println(" mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
+ pw.println(" mLastObservedLux=" + mLastObservedLux);
+ pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
+ pw.println(" mRecentLightSamples=" + mRecentLightSamples);
+ pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
pw.println(" mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
pw.println(" mShortTermModel=");
@@ -520,21 +673,22 @@ public class AutomaticBrightnessController {
}
pw.println();
+ pw.println(" mAmbientBrightnessThresholds=");
+ mAmbientBrightnessThresholds.dump(pw);
pw.println(" mScreenBrightnessThresholds=");
mScreenBrightnessThresholds.dump(pw);
pw.println(" mScreenBrightnessThresholdsIdle=");
mScreenBrightnessThresholdsIdle.dump(pw);
-
- pw.println();
- mLightSensorController.dump(pw);
+ pw.println(" mAmbientBrightnessThresholdsIdle=");
+ mAmbientBrightnessThresholdsIdle.dump(pw);
}
public float[] getLastSensorValues() {
- return mLightSensorController.getLastSensorValues();
+ return mAmbientLightRingBuffer.getAllLuxValues();
}
public long[] getLastSensorTimestamps() {
- return mLightSensorController.getLastSensorTimestamps();
+ return mAmbientLightRingBuffer.getAllTimestamps();
}
private String configStateToString(int state) {
@@ -551,33 +705,273 @@ public class AutomaticBrightnessController {
}
private boolean setLightSensorEnabled(boolean enable) {
- if (enable && mLightSensorController.enableLightSensorIfNeeded()) {
- registerForegroundAppUpdater();
- return true;
- } else if (!enable && mLightSensorController.disableLightSensorIfNeeded()) {
+ if (enable) {
+ if (!mLightSensorEnabled) {
+ mLightSensorEnabled = true;
+ mLightSensorEnableTime = mClock.uptimeMillis();
+ mCurrentLightSensorRate = mInitialLightSensorRate;
+ registerForegroundAppUpdater();
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ mCurrentLightSensorRate * 1000, mHandler);
+ return true;
+ }
+ } else if (mLightSensorEnabled) {
+ mLightSensorEnabled = false;
+ mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
+ if (!mAmbientLuxValid) {
+ mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mRawScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mAmbientLuxValid = mLightSensorController.hasValidAmbientLux();
+ mRecentLightSamples = 0;
+ mAmbientLightRingBuffer.clear();
+ mCurrentLightSensorRate = -1;
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
unregisterForegroundAppUpdater();
+ mSensorManager.unregisterListener(mLightSensorListener);
}
return false;
}
+ private void handleLightSensorEvent(long time, float lux) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
+
+ if (mAmbientLightRingBuffer.size() == 0) {
+ // switch to using the steady-state sample rate after grabbing the initial light sample
+ adjustLightSensorRate(mNormalLightSensorRate);
+ }
+ applyLightSensorMeasurement(time, lux);
+ updateAmbientLux(time);
+ }
+
+ private void applyLightSensorMeasurement(long time, float lux) {
+ mRecentLightSamples++;
+ mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
+ mAmbientLightRingBuffer.push(time, lux);
+
+ // Remember this sample value.
+ mLastObservedLux = lux;
+ mLastObservedLuxTime = time;
+ }
+
+ private void adjustLightSensorRate(int lightSensorRate) {
+ // if the light sensor rate changed, update the sensor listener
+ if (lightSensorRate != mCurrentLightSensorRate) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "adjustLightSensorRate: " +
+ "previousRate=" + mCurrentLightSensorRate + ", " +
+ "currentRate=" + lightSensorRate);
+ }
+ mCurrentLightSensorRate = lightSensorRate;
+ mSensorManager.unregisterListener(mLightSensorListener);
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ lightSensorRate * 1000, mHandler);
+ }
+ }
+
private boolean setAutoBrightnessAdjustment(float adjustment) {
return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
}
private void setAmbientLux(float lux) {
- // called by LightSensorController.setAmbientLux
- mAmbientLuxValid = true;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "setAmbientLux(" + lux + ")");
+ }
+ if (lux < 0) {
+ Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0");
+ lux = 0;
+ }
mAmbientLux = lux;
+ if (isInIdleMode()) {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
+ } else {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+ }
mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
mBrightnessClamperController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
mShortTermModel.maybeReset(mAmbientLux);
- updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ }
+
+ private float calculateAmbientLux(long now, long horizon) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")");
+ }
+ final int N = mAmbientLightRingBuffer.size();
+ if (N == 0) {
+ Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
+ return -1;
+ }
+
+ // Find the first measurement that is just outside of the horizon.
+ int endIndex = 0;
+ final long horizonStartTime = now - horizon;
+ for (int i = 0; i < N-1; i++) {
+ if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
+ endIndex++;
+ } else {
+ break;
+ }
+ }
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" +
+ mAmbientLightRingBuffer.getTime(endIndex) + ", " +
+ mAmbientLightRingBuffer.getLux(endIndex) + ")");
+ }
+ float sum = 0;
+ float totalWeight = 0;
+ long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
+ for (int i = N - 1; i >= endIndex; i--) {
+ long eventTime = mAmbientLightRingBuffer.getTime(i);
+ if (i == endIndex && eventTime < horizonStartTime) {
+ // If we're at the final value, make sure we only consider the part of the sample
+ // within our desired horizon.
+ eventTime = horizonStartTime;
+ }
+ final long startTime = eventTime - now;
+ float weight = calculateWeight(startTime, endTime);
+ float lux = mAmbientLightRingBuffer.getLux(i);
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " +
+ "lux=" + lux + ", " +
+ "weight=" + weight);
+ }
+ totalWeight += weight;
+ sum += lux * weight;
+ endTime = startTime;
+ }
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: " +
+ "totalWeight=" + totalWeight + ", " +
+ "newAmbientLux=" + (sum / totalWeight));
+ }
+ return sum / totalWeight;
+ }
+
+ private float calculateWeight(long startDelta, long endDelta) {
+ return weightIntegral(endDelta) - weightIntegral(startDelta);
+ }
+
+ // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
+ // horizon we're looking at and provides a non-linear weighting for light samples.
+ private float weightIntegral(long x) {
+ return x * (x * 0.5f + mWeightingIntercept);
+ }
+
+ private long nextAmbientLightBrighteningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + (isInIdleMode()
+ ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig);
+ }
+
+ private long nextAmbientLightDarkeningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + (isInIdleMode()
+ ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig);
+ }
+
+ private void updateAmbientLux() {
+ long time = mClock.uptimeMillis();
+ mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
+ updateAmbientLux(time);
+ }
+
+ private void updateAmbientLux(long time) {
+ // If the light sensor was just turned on then immediately update our initial
+ // estimate of the current ambient light level.
+ if (!mAmbientLuxValid) {
+ final long timeWhenSensorWarmedUp =
+ mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+ if (time < timeWhenSensorWarmedUp) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: "
+ + "time=" + time + ", "
+ + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
+ timeWhenSensorWarmedUp);
+ return;
+ }
+ setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort));
+ mAmbientLuxValid = true;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Initializing: " +
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
+ "mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ }
+
+ long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term
+ // change in lighting conditions, and a fast ambient lux to determine what the new
+ // brightness situation is since the slow lux can be quite slow to converge.
+ //
+ // Note that both values need to be checked for sufficient change before updating the
+ // proposed ambient light value since the slow value might be sufficiently far enough away
+ // from the fast value to cause a recalculation while its actually just converging on
+ // the fast value still.
+ mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+ mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+
+ if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+ && mFastAmbientLux >= mAmbientBrighteningThreshold
+ && nextBrightenTransition <= time)
+ || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+ && mFastAmbientLux <= mAmbientDarkeningThreshold
+ && nextDarkenTransition <= time)) {
+ mPreThresholdLux = mAmbientLux;
+ setAmbientLux(mFastAmbientLux);
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: "
+ + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
+ + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ + "mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ }
+ long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
+ // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
+ // exceed the necessary threshold, then it's possible we'll get a transition time prior to
+ // now. Rather than continually checking to see whether the weighted lux exceeds the
+ // threshold, schedule an update for when we'd normally expect another light sample, which
+ // should be enough time to decide whether we should actually transition to the new
+ // weighted ambient lux or not.
+ nextTransitionTime =
+ nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " +
+ nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
}
private void updateAutoBrightness(boolean sendUpdate, boolean isManuallySet) {
@@ -678,7 +1072,8 @@ public class AutomaticBrightnessController {
if (mLoggingEnabled) {
Slog.d(TAG, "Auto-brightness adjustment changed by user: "
+ "lux=" + mAmbientLux + ", "
- + "brightness=" + mScreenAutoBrightness);
+ + "brightness=" + mScreenAutoBrightness + ", "
+ + "ring=" + mAmbientLightRingBuffer);
}
EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
@@ -808,7 +1203,6 @@ public class AutomaticBrightnessController {
if (mode == AUTO_BRIGHTNESS_MODE_IDLE
|| mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
switchModeAndShortTermModels(mode);
- mLightSensorController.setIdleMode(isInIdleMode());
} else {
resetShortTermModel();
mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode);
@@ -965,6 +1359,10 @@ public class AutomaticBrightnessController {
updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/);
break;
+ case MSG_UPDATE_AMBIENT_LUX:
+ updateAmbientLux();
+ break;
+
case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
collectBrightnessAdjustmentSample();
break;
@@ -988,6 +1386,22 @@ public class AutomaticBrightnessController {
}
}
+ private final SensorEventListener mLightSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mLightSensorEnabled) {
+ final long time = mClock.uptimeMillis();
+ final float lux = event.values[0];
+ handleLightSensorEvent(time, lux);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+
// Call back whenever the tasks stack changes, which includes tasks being created, removed, and
// moving to top.
class TaskStackListenerImpl extends TaskStackListener {
@@ -1002,13 +1416,192 @@ public class AutomaticBrightnessController {
void updateBrightness();
}
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ /**
+ * A ring buffer of ambient light measurements sorted by time.
+ *
+ * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted
+ * from oldest to newest.
+ */
+ private static final class AmbientLightRingBuffer {
+ // Proportional extra capacity of the buffer beyond the expected number of light samples
+ // in the horizon
+ private static final float BUFFER_SLACK = 1.5f;
+ private float[] mRingLux;
+ private long[] mRingTime;
+ private int mCapacity;
+
+ // The first valid element and the next open slot.
+ // Note that if mCount is zero then there are no valid elements.
+ private int mStart;
+ private int mEnd;
+ private int mCount;
+ Clock mClock;
+
+ public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) {
+ if (lightSensorRate <= 0) {
+ throw new IllegalArgumentException("lightSensorRate must be above 0");
+ }
+ mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
+ mRingLux = new float[mCapacity];
+ mRingTime = new long[mCapacity];
+ mClock = clock;
+ }
+
+ public float getLux(int index) {
+ return mRingLux[offsetOf(index)];
+ }
+
+ public float[] getAllLuxValues() {
+ float[] values = new float[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingLux, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
+ public long getTime(int index) {
+ return mRingTime[offsetOf(index)];
+ }
+
+ public long[] getAllTimestamps() {
+ long[] values = new long[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingTime, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
+ public void push(long time, float lux) {
+ int next = mEnd;
+ if (mCount == mCapacity) {
+ int newSize = mCapacity * 2;
+
+ float[] newRingLux = new float[newSize];
+ long[] newRingTime = new long[newSize];
+ int length = mCapacity - mStart;
+ System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
+ System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
+ if (mStart != 0) {
+ System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
+ System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
+ }
+ mRingLux = newRingLux;
+ mRingTime = newRingTime;
+
+ next = mCapacity;
+ mCapacity = newSize;
+ mStart = 0;
+ }
+ mRingTime[next] = time;
+ mRingLux[next] = lux;
+ mEnd = next + 1;
+ if (mEnd == mCapacity) {
+ mEnd = 0;
+ }
+ mCount++;
+ }
+
+ public void prune(long horizon) {
+ if (mCount == 0) {
+ return;
+ }
+
+ while (mCount > 1) {
+ int next = mStart + 1;
+ if (next >= mCapacity) {
+ next -= mCapacity;
+ }
+ if (mRingTime[next] > horizon) {
+ // Some light sensors only produce data upon a change in the ambient light
+ // levels, so we need to consider the previous measurement as the ambient light
+ // level for all points in time up until we receive a new measurement. Thus, we
+ // always want to keep the youngest element that would be removed from the
+ // buffer and just set its measurement time to the horizon time since at that
+ // point it is the ambient light level, and to remove it would be to drop a
+ // valid data point within our horizon.
+ break;
+ }
+ mStart = next;
+ mCount -= 1;
+ }
+
+ if (mRingTime[mStart] < horizon) {
+ mRingTime[mStart] = horizon;
+ }
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public void clear() {
+ mStart = 0;
+ mEnd = 0;
+ mCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append('[');
+ for (int i = 0; i < mCount; i++) {
+ final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis();
+ if (i != 0) {
+ buf.append(", ");
+ }
+ buf.append(getLux(i));
+ buf.append(" / ");
+ buf.append(next - getTime(i));
+ buf.append("ms");
+ }
+ buf.append(']');
+ return buf.toString();
+ }
+
+ private int offsetOf(int index) {
+ if (index >= mCount || index < 0) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ index += mStart;
+ if (index >= mCapacity) {
+ index -= mCapacity;
+ }
+ return index;
+ }
+ }
+
public static class Injector {
public Handler getBackgroundThreadHandler() {
return BackgroundThread.getHandler();
}
Clock createClock() {
- return Clock.SYSTEM_CLOCK;
+ return SystemClock::uptimeMillis;
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 641b6a2f170b..61ecb934dd38 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1930,7 +1930,6 @@ public class DisplayDeviceConfig {
*
* @return true if even dimmer mode is enabled
*/
- @VisibleForTesting
public boolean isEvenDimmerAvailable() {
return mEvenDimmerBrightnessData != null;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 043ff0e9e394..b21a89a92803 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -82,7 +82,6 @@ import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
@@ -1050,13 +1049,102 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
if (defaultModeBrightnessMapper != null) {
+ // Ambient Lux - Active Mode Brightness Thresholds
+ float[] ambientBrighteningThresholds =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+ float[] ambientDarkeningThresholds =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+ float[] ambientBrighteningLevels =
+ mDisplayDeviceConfig.getAmbientBrighteningLevels();
+ float[] ambientDarkeningLevels =
+ mDisplayDeviceConfig.getAmbientDarkeningLevels();
+ float ambientDarkeningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+ float ambientBrighteningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningMinThreshold);
+
// Display - Active Mode Brightness Thresholds
- HysteresisLevels screenBrightnessThresholds =
- mInjector.getBrightnessThresholdsHysteresisLevels(mDisplayDeviceConfig);
+ float[] screenBrighteningThresholds =
+ mDisplayDeviceConfig.getScreenBrighteningPercentages();
+ float[] screenDarkeningThresholds =
+ mDisplayDeviceConfig.getScreenDarkeningPercentages();
+ float[] screenBrighteningLevels =
+ mDisplayDeviceConfig.getScreenBrighteningLevels();
+ float[] screenDarkeningLevels =
+ mDisplayDeviceConfig.getScreenDarkeningLevels();
+ float screenDarkeningMinThreshold =
+ mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+ float screenBrighteningMinThreshold =
+ mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds,
+ screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+ screenBrighteningMinThreshold, true);
+
+ // Ambient Lux - Idle Screen Brightness Thresholds
+ float ambientDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+ float ambientBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ float[] ambientBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+ float[] ambientDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+ float[] ambientBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+ float[] ambientDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
+ ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+ ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+ ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
// Display - Idle Screen Brightness Thresholds
- HysteresisLevels screenBrightnessThresholdsIdle =
- mInjector.getBrightnessThresholdsIdleHysteresisLevels(mDisplayDeviceConfig);
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ float[] screenBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+ float[] screenDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+ float[] screenBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+ float[] screenDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
+ screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+ screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+ long brighteningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounce();
+ long darkeningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounce();
+ long brighteningLightDebounceIdle = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounceIdle();
+ long darkeningLightDebounceIdle = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounceIdle();
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
+ R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
+ R.integer.config_lightSensorWarmupTime);
+ int lightSensorRate = context.getResources().getInteger(
+ R.integer.config_autoBrightnessLightSensorRate);
+ int initialLightSensorRate = context.getResources().getInteger(
+ R.integer.config_autoBrightnessInitialLightSensorRate);
+ if (initialLightSensorRate == -1) {
+ initialLightSensorRate = lightSensorRate;
+ } else if (initialLightSensorRate > lightSensorRate) {
+ Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+ + initialLightSensorRate + ") to be less than or equal to "
+ + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
+ }
loadAmbientLightSensor();
// BrightnessTracker should only use one light sensor, we want to use the light sensor
@@ -1068,15 +1156,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
-
- LightSensorController.LightSensorControllerConfig config =
- mInjector.getLightSensorControllerConfig(context, mDisplayDeviceConfig);
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
- this, handler.getLooper(), mSensorManager, brightnessMappers,
- PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor,
- screenBrightnessThresholds, screenBrightnessThresholdsIdle,
- mContext, mBrightnessRangeController,
- mBrightnessThrottler, userLux, userNits, mDisplayId, config,
+ this, handler.getLooper(), mSensorManager, mLightSensor,
+ brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
+ initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
+ brighteningLightDebounceIdle, darkeningLightDebounceIdle,
+ autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
+ mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
mBrightnessClamperController);
mDisplayBrightnessController.setAutomaticBrightnessController(
mAutomaticBrightnessController);
@@ -3083,34 +3173,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
- return new AutomaticBrightnessController(callbacks, looper, sensorManager,
- brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor,
- screenBrightnessThresholds, screenBrightnessThresholdsIdle, context,
- brightnessModeController, brightnessThrottler, userLux, userNits, displayId,
- config, brightnessClamperController);
- }
-
- LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig(
- Context context, DisplayDeviceConfig displayDeviceConfig) {
- return LightSensorController.LightSensorControllerConfig.create(
- context.getResources(), displayDeviceConfig);
- }
- HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) {
- return HysteresisLevels.getBrightnessThresholdsIdle(ddc);
- }
-
- HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) {
- return HysteresisLevels.getBrightnessThresholds(ddc);
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, brightnessModeController,
+ brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+ userNits, brightnessClamperController);
}
BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
@@ -3120,6 +3208,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
+
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager,
Sensor lightSensor,
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index bb349e76857c..0521b8ac4f3b 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,7 +18,6 @@ package com.android.server.display;
import android.util.Slog;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
@@ -53,8 +52,7 @@ public class HysteresisLevels {
* @param potentialOldBrightnessRange whether or not the values used could be from the old
* screen brightness range ie, between 1-255.
*/
- @VisibleForTesting
- public HysteresisLevels(float[] brighteningThresholdsPercentages,
+ HysteresisLevels(float[] brighteningThresholdsPercentages,
float[] darkeningThresholdsPercentages,
float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
float minDarkeningThreshold, float minBrighteningThreshold,
@@ -140,10 +138,7 @@ public class HysteresisLevels {
return levelArray;
}
- /**
- * Print the object's debug information into the given stream.
- */
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
pw.println(" mBrighteningThresholdsPercentages="
@@ -154,45 +149,4 @@ public class HysteresisLevels {
+ Arrays.toString(mDarkeningThresholdsPercentages));
pw.println(" mMinDarkening=" + mMinDarkening);
}
-
-
- /**
- * Creates hysteresis levels for Active Ambient Lux
- */
- public static HysteresisLevels getAmbientBrightnessThresholds(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getAmbientBrighteningPercentages(),
- ddc.getAmbientDarkeningPercentages(), ddc.getAmbientBrighteningLevels(),
- ddc.getAmbientDarkeningLevels(), ddc.getAmbientLuxDarkeningMinThreshold(),
- ddc.getAmbientLuxBrighteningMinThreshold());
- }
-
- /**
- * Creates hysteresis levels for Active Screen Brightness
- */
- public static HysteresisLevels getBrightnessThresholds(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getScreenBrighteningPercentages(),
- ddc.getScreenDarkeningPercentages(), ddc.getScreenBrighteningLevels(),
- ddc.getScreenDarkeningLevels(), ddc.getScreenDarkeningMinThreshold(),
- ddc.getScreenBrighteningMinThreshold(), true);
- }
-
- /**
- * Creates hysteresis levels for Idle Ambient Lux
- */
- public static HysteresisLevels getAmbientBrightnessThresholdsIdle(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getAmbientBrighteningPercentagesIdle(),
- ddc.getAmbientDarkeningPercentagesIdle(), ddc.getAmbientBrighteningLevelsIdle(),
- ddc.getAmbientDarkeningLevelsIdle(), ddc.getAmbientLuxDarkeningMinThresholdIdle(),
- ddc.getAmbientLuxBrighteningMinThresholdIdle());
- }
-
- /**
- * Creates hysteresis levels for Idle Screen Brightness
- */
- public static HysteresisLevels getBrightnessThresholdsIdle(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getScreenBrighteningPercentagesIdle(),
- ddc.getScreenDarkeningPercentagesIdle(), ddc.getScreenBrighteningLevelsIdle(),
- ddc.getScreenDarkeningLevelsIdle(), ddc.getScreenDarkeningMinThresholdIdle(),
- ddc.getScreenBrighteningMinThresholdIdle());
- }
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index a577e225076f..1dfe03735595 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -940,7 +940,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
final float nits = backlightToNits(backlight);
final float sdrNits = backlightToNits(sdrBacklight);
- if (getFeatureFlags().isEvenDimmerEnabled()) {
+ if (getFeatureFlags().isEvenDimmerEnabled()
+ && mDisplayDeviceConfig != null
+ && mDisplayDeviceConfig.isEvenDimmerAvailable()) {
applyColorMatrixBasedDimming(brightnessState);
}
diff --git a/services/core/java/com/android/server/display/brightness/LightSensorController.java b/services/core/java/com/android/server/display/brightness/LightSensorController.java
deleted file mode 100644
index d82d6983c3ba..000000000000
--- a/services/core/java/com/android/server/display/brightness/LightSensorController.java
+++ /dev/null
@@ -1,868 +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.server.display.brightness;
-
-import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX;
-
-import android.annotation.Nullable;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Display;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.Clock;
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.HysteresisLevels;
-import com.android.server.display.config.SensorData;
-import com.android.server.display.utils.SensorUtils;
-
-import java.io.PrintWriter;
-
-/**
- * Manages light sensor subscription and notifies its listeners about ambient lux changes based on
- * configuration
- */
-public class LightSensorController {
- // How long the current sensor reading is assumed to be valid beyond the current time.
- // This provides a bit of prediction, as well as ensures that the weight for the last sample is
- // non-zero, which in turn ensures that the total weight is non-zero.
- private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
-
- // Proportional extra capacity of the buffer beyond the expected number of light samples
- // in the horizon
- private static final float BUFFER_SLACK = 1.5f;
-
- private boolean mLoggingEnabled;
- private boolean mLightSensorEnabled;
- private long mLightSensorEnableTime;
- // The current light sensor event rate in milliseconds.
- private int mCurrentLightSensorRate = -1;
- // The number of light samples collected since the light sensor was enabled.
- private int mRecentLightSamples;
- private float mAmbientLux;
- // True if mAmbientLux holds a valid value.
- private boolean mAmbientLuxValid;
- // The last ambient lux value prior to passing the darkening or brightening threshold.
- private float mPreThresholdLux;
- // The most recent light sample.
- private float mLastObservedLux = INVALID_LUX;
- // The time of the most light recent sample.
- private long mLastObservedLuxTime;
- // The last calculated ambient light level (long time window).
- private float mSlowAmbientLux;
- // The last calculated ambient light level (short time window).
- private float mFastAmbientLux;
- private volatile boolean mIsIdleMode;
- // The ambient light level threshold at which to brighten or darken the screen.
- private float mAmbientBrighteningThreshold;
- private float mAmbientDarkeningThreshold;
-
- private final LightSensorControllerConfig mConfig;
-
- // The light sensor, or null if not available or needed.
- @Nullable
- private final Sensor mLightSensor;
-
- // A ring buffer containing all of the recent ambient light sensor readings.
- private final AmbientLightRingBuffer mAmbientLightRingBuffer;
-
- private final Injector mInjector;
-
- private final SensorEventListener mLightSensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mLightSensorEnabled) {
- final long time = mClock.uptimeMillis();
- final float lux = event.values[0];
- handleLightSensorEvent(time, lux);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Not used.
- }
- };
-
- // Runnable used to delay ambient lux update when:
- // 1) update triggered before configured warm up time
- // 2) next brightening or darkening transition need to happen
- private final Runnable mAmbientLuxUpdater = this::updateAmbientLux;
-
- private final Clock mClock;
-
- private final Handler mHandler;
-
- private final String mTag;
-
- private LightSensorListener mListener;
-
- public LightSensorController(
- SensorManager sensorManager,
- Looper looper,
- int displayId,
- LightSensorControllerConfig config) {
- this(config, new RealInjector(sensorManager, displayId), new LightSensorHandler(looper));
- }
-
- @VisibleForTesting
- LightSensorController(
- LightSensorControllerConfig config,
- Injector injector,
- Handler handler) {
- if (config.mNormalLightSensorRate <= 0) {
- throw new IllegalArgumentException("lightSensorRate must be above 0");
- }
- mInjector = injector;
- int bufferInitialCapacity = (int) Math.ceil(
- config.mAmbientLightHorizonLong * BUFFER_SLACK / config.mNormalLightSensorRate);
- mClock = injector.getClock();
- mHandler = handler;
- mAmbientLightRingBuffer = new AmbientLightRingBuffer(bufferInitialCapacity, mClock);
- mConfig = config;
- mLightSensor = mInjector.getLightSensor(mConfig);
- mTag = mInjector.getTag();
- }
-
- public void setListener(LightSensorListener listener) {
- mListener = listener;
- }
-
- /**
- * @return true if sensor registered, false if sensor already registered
- */
- public boolean enableLightSensorIfNeeded() {
- if (!mLightSensorEnabled) {
- mLightSensorEnabled = true;
- mLightSensorEnableTime = mClock.uptimeMillis();
- mCurrentLightSensorRate = mConfig.mInitialLightSensorRate;
- mInjector.registerLightSensorListener(
- mLightSensorListener, mLightSensor, mCurrentLightSensorRate, mHandler);
- return true;
- }
- return false;
- }
-
- /**
- * @return true if sensor unregistered, false if sensor already unregistered
- */
- public boolean disableLightSensorIfNeeded() {
- if (mLightSensorEnabled) {
- mLightSensorEnabled = false;
- mAmbientLuxValid = !mConfig.mResetAmbientLuxAfterWarmUpConfig;
- if (!mAmbientLuxValid) {
- mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- }
- mRecentLightSamples = 0;
- mAmbientLightRingBuffer.clear();
- mCurrentLightSensorRate = -1;
- mInjector.unregisterLightSensorListener(mLightSensorListener);
- return true;
- }
- return false;
- }
-
- public void setLoggingEnabled(boolean loggingEnabled) {
- mLoggingEnabled = loggingEnabled;
- }
-
- /**
- * Updates BrightnessEvent with LightSensorController details
- */
- public void updateBrightnessEvent(BrightnessEvent brightnessEvent) {
- brightnessEvent.setPreThresholdLux(mPreThresholdLux);
- }
-
- /**
- * Print the object's debug information into the given stream.
- */
- public void dump(PrintWriter pw) {
- pw.println("LightSensorController state:");
- pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
- pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
- pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate);
- pw.println(" mRecentLightSamples=" + mRecentLightSamples);
- pw.println(" mAmbientLux=" + mAmbientLux);
- pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
- pw.println(" mPreThresholdLux=" + mPreThresholdLux);
- pw.println(" mLastObservedLux=" + mLastObservedLux);
- pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
- pw.println(" mSlowAmbientLux=" + mSlowAmbientLux);
- pw.println(" mFastAmbientLux=" + mFastAmbientLux);
- pw.println(" mIsIdleMode=" + mIsIdleMode);
- pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
- pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
- pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
- pw.println(" mLightSensor=" + mLightSensor);
- mConfig.dump(pw);
- }
-
- /**
- * This method should be called when this LightSensorController is no longer in use
- * i.e. when corresponding display removed
- */
- public void stop() {
- mHandler.removeCallbacksAndMessages(null);
- disableLightSensorIfNeeded();
- }
-
- public void setIdleMode(boolean isIdleMode) {
- mIsIdleMode = isIdleMode;
- }
-
- /**
- * returns true if LightSensorController holds valid ambient lux value
- */
- public boolean hasValidAmbientLux() {
- return mAmbientLuxValid;
- }
-
- /**
- * returns all last observed sensor values
- */
- public float[] getLastSensorValues() {
- return mAmbientLightRingBuffer.getAllLuxValues();
- }
-
- /**
- * returns all last observed sensor event timestamps
- */
- public long[] getLastSensorTimestamps() {
- return mAmbientLightRingBuffer.getAllTimestamps();
- }
-
- public float getLastObservedLux() {
- return mLastObservedLux;
- }
-
- private void handleLightSensorEvent(long time, float lux) {
- Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
- mHandler.removeCallbacks(mAmbientLuxUpdater);
-
- if (mAmbientLightRingBuffer.size() == 0) {
- // switch to using the steady-state sample rate after grabbing the initial light sample
- adjustLightSensorRate(mConfig.mNormalLightSensorRate);
- }
- applyLightSensorMeasurement(time, lux);
- updateAmbientLux(time);
- }
-
- private void applyLightSensorMeasurement(long time, float lux) {
- mRecentLightSamples++;
- mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong);
- mAmbientLightRingBuffer.push(time, lux);
- // Remember this sample value.
- mLastObservedLux = lux;
- mLastObservedLuxTime = time;
- }
-
- private void adjustLightSensorRate(int lightSensorRate) {
- // if the light sensor rate changed, update the sensor listener
- if (lightSensorRate != mCurrentLightSensorRate) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "adjustLightSensorRate: "
- + "previousRate=" + mCurrentLightSensorRate + ", "
- + "currentRate=" + lightSensorRate);
- }
- mCurrentLightSensorRate = lightSensorRate;
- mInjector.unregisterLightSensorListener(mLightSensorListener);
- mInjector.registerLightSensorListener(
- mLightSensorListener, mLightSensor, lightSensorRate, mHandler);
- }
- }
-
- private void setAmbientLux(float lux) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "setAmbientLux(" + lux + ")");
- }
- if (lux < 0) {
- Slog.w(mTag, "Ambient lux was negative, ignoring and setting to 0");
- lux = 0;
- }
- mAmbientLux = lux;
-
- if (mIsIdleMode) {
- mAmbientBrighteningThreshold =
- mConfig.mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
- mAmbientDarkeningThreshold =
- mConfig.mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
- } else {
- mAmbientBrighteningThreshold =
- mConfig.mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
- mAmbientDarkeningThreshold =
- mConfig.mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
- }
-
- mListener.onAmbientLuxChange(mAmbientLux);
- }
-
- private float calculateAmbientLux(long now, long horizon) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux(" + now + ", " + horizon + ")");
- }
- final int size = mAmbientLightRingBuffer.size();
- if (size == 0) {
- Slog.e(mTag, "calculateAmbientLux: No ambient light readings available");
- return -1;
- }
-
- // Find the first measurement that is just outside of the horizon.
- int endIndex = 0;
- final long horizonStartTime = now - horizon;
- for (int i = 0; i < size - 1; i++) {
- if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
- endIndex++;
- } else {
- break;
- }
- }
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=("
- + mAmbientLightRingBuffer.getTime(endIndex) + ", "
- + mAmbientLightRingBuffer.getLux(endIndex) + ")");
- }
- float sum = 0;
- float totalWeight = 0;
- long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
- for (int i = size - 1; i >= endIndex; i--) {
- long eventTime = mAmbientLightRingBuffer.getTime(i);
- if (i == endIndex && eventTime < horizonStartTime) {
- // If we're at the final value, make sure we only consider the part of the sample
- // within our desired horizon.
- eventTime = horizonStartTime;
- }
- final long startTime = eventTime - now;
- float weight = calculateWeight(startTime, endTime);
- float lux = mAmbientLightRingBuffer.getLux(i);
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: "
- + "lux=" + lux + ", "
- + "weight=" + weight);
- }
- totalWeight += weight;
- sum += lux * weight;
- endTime = startTime;
- }
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: "
- + "totalWeight=" + totalWeight + ", "
- + "newAmbientLux=" + (sum / totalWeight));
- }
- return sum / totalWeight;
- }
-
- private float calculateWeight(long startDelta, long endDelta) {
- return weightIntegral(endDelta) - weightIntegral(startDelta);
- }
-
- // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
- // horizon we're looking at and provides a non-linear weighting for light samples.
- private float weightIntegral(long x) {
- return x * (x * 0.5f + mConfig.mWeightingIntercept);
- }
-
- private long nextAmbientLightBrighteningTransition(long time) {
- final int size = mAmbientLightRingBuffer.size();
- long earliestValidTime = time;
- for (int i = size - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
- break;
- }
- earliestValidTime = mAmbientLightRingBuffer.getTime(i);
- }
- return earliestValidTime + (mIsIdleMode ? mConfig.mBrighteningLightDebounceConfigIdle
- : mConfig.mBrighteningLightDebounceConfig);
- }
-
- private long nextAmbientLightDarkeningTransition(long time) {
- final int size = mAmbientLightRingBuffer.size();
- long earliestValidTime = time;
- for (int i = size - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
- break;
- }
- earliestValidTime = mAmbientLightRingBuffer.getTime(i);
- }
- return earliestValidTime + (mIsIdleMode ? mConfig.mDarkeningLightDebounceConfigIdle
- : mConfig.mDarkeningLightDebounceConfig);
- }
-
- private void updateAmbientLux() {
- long time = mClock.uptimeMillis();
- mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong);
- updateAmbientLux(time);
- }
-
- private void updateAmbientLux(long time) {
- // If the light sensor was just turned on then immediately update our initial
- // estimate of the current ambient light level.
- if (!mAmbientLuxValid) {
- final long timeWhenSensorWarmedUp =
- mConfig.mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
- if (time < timeWhenSensorWarmedUp) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Sensor not ready yet: "
- + "time=" + time + ", "
- + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
- }
- mHandler.postAtTime(mAmbientLuxUpdater, timeWhenSensorWarmedUp);
- return;
- }
- mAmbientLuxValid = true;
- setAmbientLux(calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort));
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Initializing: "
- + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
- + "mAmbientLux=" + mAmbientLux);
- }
- }
-
- long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
- long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
- // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term
- // change in lighting conditions, and a fast ambient lux to determine what the new
- // brightness situation is since the slow lux can be quite slow to converge.
- //
- // Note that both values need to be checked for sufficient change before updating the
- // proposed ambient light value since the slow value might be sufficiently far enough away
- // from the fast value to cause a recalculation while its actually just converging on
- // the fast value still.
- mSlowAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonLong);
- mFastAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort);
-
- if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
- && mFastAmbientLux >= mAmbientBrighteningThreshold
- && nextBrightenTransition <= time)
- || (mSlowAmbientLux <= mAmbientDarkeningThreshold
- && mFastAmbientLux <= mAmbientDarkeningThreshold
- && nextDarkenTransition <= time)) {
- mPreThresholdLux = mAmbientLux;
- setAmbientLux(mFastAmbientLux);
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: "
- + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
- + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
- + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
- + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
- + "mAmbientLux=" + mAmbientLux);
- }
- nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
- nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
- }
- long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
- // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
- // exceed the necessary threshold, then it's possible we'll get a transition time prior to
- // now. Rather than continually checking to see whether the weighted lux exceeds the
- // threshold, schedule an update for when we'd normally expect another light sample, which
- // should be enough time to decide whether we should actually transition to the new
- // weighted ambient lux or not.
- nextTransitionTime = nextTransitionTime > time ? nextTransitionTime
- : time + mConfig.mNormalLightSensorRate;
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Scheduling ambient lux update for "
- + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
- }
- mHandler.postAtTime(mAmbientLuxUpdater, nextTransitionTime);
- }
-
- public interface LightSensorListener {
- /**
- * Called when new ambient lux value is ready
- */
- void onAmbientLuxChange(float ambientLux);
- }
-
- private static final class LightSensorHandler extends Handler {
- private LightSensorHandler(Looper looper) {
- super(looper, /* callback= */ null, /* async= */ true);
- }
- }
-
- /**
- * A ring buffer of ambient light measurements sorted by time.
- * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted
- * from oldest to newest.
- */
- @VisibleForTesting
- static final class AmbientLightRingBuffer {
-
- private float[] mRingLux;
- private long[] mRingTime;
- private int mCapacity;
-
- // The first valid element and the next open slot.
- // Note that if mCount is zero then there are no valid elements.
- private int mStart;
- private int mEnd;
- private int mCount;
-
- private final Clock mClock;
-
- @VisibleForTesting
- AmbientLightRingBuffer(int initialCapacity, Clock clock) {
- mCapacity = initialCapacity;
- mRingLux = new float[mCapacity];
- mRingTime = new long[mCapacity];
- mClock = clock;
-
- }
-
- @VisibleForTesting
- float getLux(int index) {
- return mRingLux[offsetOf(index)];
- }
-
- @VisibleForTesting
- float[] getAllLuxValues() {
- float[] values = new float[mCount];
- if (mCount == 0) {
- return values;
- }
-
- if (mStart < mEnd) {
- System.arraycopy(mRingLux, mStart, values, 0, mCount);
- } else {
- System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
- System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
- }
-
- return values;
- }
-
- @VisibleForTesting
- long getTime(int index) {
- return mRingTime[offsetOf(index)];
- }
-
- @VisibleForTesting
- long[] getAllTimestamps() {
- long[] values = new long[mCount];
- if (mCount == 0) {
- return values;
- }
-
- if (mStart < mEnd) {
- System.arraycopy(mRingTime, mStart, values, 0, mCount);
- } else {
- System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
- System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
- }
-
- return values;
- }
-
- @VisibleForTesting
- void push(long time, float lux) {
- int next = mEnd;
- if (mCount == mCapacity) {
- int newSize = mCapacity * 2;
-
- float[] newRingLux = new float[newSize];
- long[] newRingTime = new long[newSize];
- int length = mCapacity - mStart;
- System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
- System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
- if (mStart != 0) {
- System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
- System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
- }
- mRingLux = newRingLux;
- mRingTime = newRingTime;
-
- next = mCapacity;
- mCapacity = newSize;
- mStart = 0;
- }
- mRingTime[next] = time;
- mRingLux[next] = lux;
- mEnd = next + 1;
- if (mEnd == mCapacity) {
- mEnd = 0;
- }
- mCount++;
- }
-
- @VisibleForTesting
- void prune(long horizon) {
- if (mCount == 0) {
- return;
- }
-
- while (mCount > 1) {
- int next = mStart + 1;
- if (next >= mCapacity) {
- next -= mCapacity;
- }
- if (mRingTime[next] > horizon) {
- // Some light sensors only produce data upon a change in the ambient light
- // levels, so we need to consider the previous measurement as the ambient light
- // level for all points in time up until we receive a new measurement. Thus, we
- // always want to keep the youngest element that would be removed from the
- // buffer and just set its measurement time to the horizon time since at that
- // point it is the ambient light level, and to remove it would be to drop a
- // valid data point within our horizon.
- break;
- }
- mStart = next;
- mCount -= 1;
- }
-
- if (mRingTime[mStart] < horizon) {
- mRingTime[mStart] = horizon;
- }
- }
-
- @VisibleForTesting
- int size() {
- return mCount;
- }
-
- @VisibleForTesting
- void clear() {
- mStart = 0;
- mEnd = 0;
- mCount = 0;
- }
-
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder();
- buf.append('[');
- for (int i = 0; i < mCount; i++) {
- final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis();
- if (i != 0) {
- buf.append(", ");
- }
- buf.append(getLux(i));
- buf.append(" / ");
- buf.append(next - getTime(i));
- buf.append("ms");
- }
- buf.append(']');
- return buf.toString();
- }
-
- private int offsetOf(int index) {
- if (index >= mCount || index < 0) {
- throw new ArrayIndexOutOfBoundsException(index);
- }
- index += mStart;
- if (index >= mCapacity) {
- index -= mCapacity;
- }
- return index;
- }
- }
-
- @VisibleForTesting
- interface Injector {
- Clock getClock();
-
- Sensor getLightSensor(LightSensorControllerConfig config);
-
- boolean registerLightSensorListener(
- SensorEventListener listener, Sensor sensor, int rate, Handler handler);
-
- void unregisterLightSensorListener(SensorEventListener listener);
-
- String getTag();
-
- }
-
- private static class RealInjector implements Injector {
- private final SensorManager mSensorManager;
- private final int mSensorFallbackType;
-
- private final String mTag;
-
- private RealInjector(SensorManager sensorManager, int displayId) {
- mSensorManager = sensorManager;
- mSensorFallbackType = displayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
- mTag = "LightSensorController [" + displayId + "]";
- }
-
- @Override
- public Clock getClock() {
- return Clock.SYSTEM_CLOCK;
- }
-
- @Override
- public Sensor getLightSensor(LightSensorControllerConfig config) {
- return SensorUtils.findSensor(
- mSensorManager, config.mAmbientLightSensor, mSensorFallbackType);
- }
-
- @Override
- public boolean registerLightSensorListener(
- SensorEventListener listener, Sensor sensor, int rate, Handler handler) {
- return mSensorManager.registerListener(listener, sensor, rate * 1000, handler);
- }
-
- @Override
- public void unregisterLightSensorListener(SensorEventListener listener) {
- mSensorManager.unregisterListener(listener);
- }
-
- @Override
- public String getTag() {
- return mTag;
- }
- }
-
- public static class LightSensorControllerConfig {
- // Steady-state light sensor event rate in milliseconds.
- private final int mNormalLightSensorRate;
- private final int mInitialLightSensorRate;
-
- // If true immediately after the screen is turned on the controller will try to adjust the
- // brightness based on the current sensor reads. If false, the controller will collect
- // more data
- // and only then decide whether to change brightness.
- private final boolean mResetAmbientLuxAfterWarmUpConfig;
-
- // Period of time in which to consider light samples for a short/long-term estimate of
- // ambient
- // light in milliseconds.
- private final int mAmbientLightHorizonShort;
- private final int mAmbientLightHorizonLong;
-
-
- // Amount of time to delay auto-brightness after screen on while waiting for
- // the light sensor to warm-up in milliseconds.
- // May be 0 if no warm-up is required.
- private final int mLightSensorWarmUpTimeConfig;
-
-
- // The intercept used for the weighting calculation. This is used in order to keep all
- // possible
- // weighting values positive.
- private final int mWeightingIntercept;
-
- // Configuration object for determining thresholds to change brightness dynamically
- private final HysteresisLevels mAmbientBrightnessThresholds;
- private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
-
-
- // Stability requirements in milliseconds for accepting a new brightness level. This is
- // used
- // for debouncing the light sensor. Different constants are used to debounce the light
- // sensor
- // when adapting to brighter or darker environments. This parameter controls how quickly
- // brightness changes occur in response to an observed change in light level that exceeds
- // the
- // hysteresis threshold.
- private final long mBrighteningLightDebounceConfig;
- private final long mDarkeningLightDebounceConfig;
- private final long mBrighteningLightDebounceConfigIdle;
- private final long mDarkeningLightDebounceConfigIdle;
-
- private final SensorData mAmbientLightSensor;
-
- @VisibleForTesting
- LightSensorControllerConfig(int initialLightSensorRate, int normalLightSensorRate,
- boolean resetAmbientLuxAfterWarmUpConfig, int ambientLightHorizonShort,
- int ambientLightHorizonLong, int lightSensorWarmUpTimeConfig,
- int weightingIntercept, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels ambientBrightnessThresholdsIdle,
- long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
- long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
- SensorData ambientLightSensor) {
- mInitialLightSensorRate = initialLightSensorRate;
- mNormalLightSensorRate = normalLightSensorRate;
- mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
- mAmbientLightHorizonShort = ambientLightHorizonShort;
- mAmbientLightHorizonLong = ambientLightHorizonLong;
- mLightSensorWarmUpTimeConfig = lightSensorWarmUpTimeConfig;
- mWeightingIntercept = weightingIntercept;
- mAmbientBrightnessThresholds = ambientBrightnessThresholds;
- mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
- mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
- mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
- mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle;
- mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle;
- mAmbientLightSensor = ambientLightSensor;
- }
-
- private void dump(PrintWriter pw) {
- pw.println("LightSensorControllerConfig:");
- pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate);
- pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate);
- pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
- pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
- pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
- pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
- pw.println(" mWeightingIntercept=" + mWeightingIntercept);
- pw.println(" mAmbientBrightnessThresholds=");
- mAmbientBrightnessThresholds.dump(pw);
- pw.println(" mAmbientBrightnessThresholdsIdle=");
- mAmbientBrightnessThresholdsIdle.dump(pw);
- pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
- pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
- pw.println(
- " mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
- pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
- pw.println(" mAmbientLightSensor=" + mAmbientLightSensor);
- }
-
- /**
- * Creates LightSensorControllerConfig object form Resources and DisplayDeviceConfig
- */
- public static LightSensorControllerConfig create(Resources res, DisplayDeviceConfig ddc) {
- int lightSensorRate = res.getInteger(R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = res.getInteger(
- R.integer.config_autoBrightnessInitialLightSensorRate);
- if (initialLightSensorRate == -1) {
- initialLightSensorRate = lightSensorRate;
- } else if (initialLightSensorRate > lightSensorRate) {
- Slog.w("LightSensorControllerConfig",
- "Expected config_autoBrightnessInitialLightSensorRate ("
- + initialLightSensorRate + ") to be less than or equal to "
- + "config_autoBrightnessLightSensorRate (" + lightSensorRate
- + ").");
- }
-
- boolean resetAmbientLuxAfterWarmUp = res.getBoolean(
- R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = res.getInteger(
- R.integer.config_lightSensorWarmupTime);
-
- return new LightSensorControllerConfig(initialLightSensorRate, lightSensorRate,
- resetAmbientLuxAfterWarmUp, ddc.getAmbientHorizonShort(),
- ddc.getAmbientHorizonLong(), lightSensorWarmUpTimeConfig,
- ddc.getAmbientHorizonLong(),
- HysteresisLevels.getAmbientBrightnessThresholds(ddc),
- HysteresisLevels.getAmbientBrightnessThresholdsIdle(ddc),
- ddc.getAutoBrightnessBrighteningLightDebounce(),
- ddc.getAutoBrightnessDarkeningLightDebounce(),
- ddc.getAutoBrightnessBrighteningLightDebounceIdle(),
- ddc.getAutoBrightnessDarkeningLightDebounceIdle(),
- ddc.getAmbientLightSensor()
- );
- }
- }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 9c7504db0cf0..a46975fb3567 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -285,7 +285,7 @@ public class BrightnessClamperController {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
- if (flags.isEvenDimmerEnabled()) {
+ if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
displayDeviceConfig));
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 095a233bde64..fd3da85e4437 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6272,9 +6272,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final int[] userIds = resolveUserIds(UserHandle.USER_ALL);
final String reason = "The mimeGroup is changed";
for (int i = 0; i < userIds.length; i++) {
- final int packageUid = UserHandle.getUid(userIds[i], appId);
- mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
- true /* dontKillApp */, components, packageUid, reason);
+ final PackageUserStateInternal pkgUserState =
+ packageState.getUserStates().get(userIds[i]);
+ if (pkgUserState != null && pkgUserState.isInstalled()) {
+ final int packageUid = UserHandle.getUid(userIds[i], appId);
+ mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
+ true /* dontKillApp */, components, packageUid, reason);
+ }
}
});
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8e3c6ac799b4..3a84897839a1 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -3844,6 +3844,7 @@ public class BatteryStatsImpl extends BatteryStats {
public abstract T instantiateObject();
}
+ @SuppressWarnings("ParcelableCreator")
public static class ControllerActivityCounterImpl extends ControllerActivityCounter
implements Parcelable {
private final Clock mClock;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17e699668d14..257dadf6c02c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8507,8 +8507,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
- final boolean isFixedOrientationLetterboxAllowed =
- parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ // Bubble activities should always fill their parent and should not be letterboxed.
+ final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
+ && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
// When starting to switch between PiP and fullscreen, the task is pinned
// and the activity is fullscreen. But only allow to apply letterbox if the
@@ -8516,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|| (!mWaitForEnteringPinnedMode
&& parentWindowingMode == WINDOWING_MODE_PINNED
&& resolvedConfig.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN);
+ == WINDOWING_MODE_FULLSCREEN));
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 933633836e56..47f4a66995af 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -783,7 +783,7 @@ public class BackgroundActivityStartController {
if (balShowToastsBlocked()
&& (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
// only show a toast if either caller or real caller could launch if they opted in
- showToast("BAL blocked. go/debug-bal");
+ showToast("BAL blocked. goo.gle/android-bal");
}
return statsLog(BalVerdict.BLOCK, state);
}
diff --git a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
index c3375236098a..180081c9173a 100644
--- a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
+++ b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
@@ -32,10 +32,10 @@ namespace android {
namespace {
-// Max size we allow for the result from HIDIOCGRAWUNIQ (Bluetooth address or USB serial number).
-// Copied from linux/hid.h struct hid_device->uniq char array size; the ioctl implementation
-// writes at most this many bytes to the provided buffer.
-constexpr int UNIQ_SIZE_MAX = 64;
+// Max sizes we allow for results from string ioctl calls, copied from UAPI linux/uhid.h.
+// The ioctl implementation writes at most this many bytes to the provided buffer:
+constexpr int NAME_SIZE_MAX = 128; // HIDIOCGRAWNAME (device name)
+constexpr int UNIQ_SIZE_MAX = 64; // HIDIOCGRAWUNIQ (BT address or USB serial number)
} // anonymous namespace
@@ -82,6 +82,16 @@ static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawB
return info.bustype;
}
+static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawName(
+ JNIEnv* env, jclass /*clazz*/, int fd) {
+ char buf[NAME_SIZE_MAX];
+ if (ioctl(fd, HIDIOCGRAWNAME(NAME_SIZE_MAX), buf) < 0) {
+ return nullptr;
+ }
+ // Local ref is not deleted because it is returned to Java
+ return env->NewStringUTF(buf);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeGetHidrawDescSize", "(I)I",
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize},
@@ -91,6 +101,8 @@ static const JNINativeMethod gMethods[] = {
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq},
{"nativeGetHidrawBusType", "(I)I",
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType},
+ {"nativeGetHidrawName", "(I)Ljava/lang/String;",
+ (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawName},
};
int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f955b91136a3..c3175d64e2d3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -888,6 +888,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
+ private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
+
/**
* For apps targeting U+
* Enable multiple admins to coexist on the same device.
@@ -21514,13 +21516,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account.");
return;
}
- copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+
+ if (Flags.copyAccountWithRetryEnabled()) {
+ boolean copySucceeded = false;
+ int retryAttemptsLeft = RETRY_COPY_ACCOUNT_ATTEMPTS;
+ while (!copySucceeded && (retryAttemptsLeft > 0)) {
+ Slogf.i(LOG_TAG, "Copying account. Attempts left : " + retryAttemptsLeft);
+ copySucceeded =
+ copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+ retryAttemptsLeft--;
+ }
+ } else {
+ copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+ }
if (!keepAccountMigrated) {
removeAccount(accountToMigrate, sourceUserId);
}
+
}
- private void copyAccount(
+ private boolean copyAccount(
UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate,
String callerPackage) {
final long startTime = SystemClock.elapsedRealtime();
@@ -21538,6 +21553,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_MS,
startTime,
callerPackage);
+ Slogf.i(LOG_TAG, "Copy account successful to " + targetUser);
+ return true;
} else {
logCopyAccountStatus(COPY_ACCOUNT_FAILED, callerPackage);
Slogf.e(LOG_TAG, "Failed to copy account to " + targetUser);
@@ -21550,6 +21567,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
logCopyAccountStatus(COPY_ACCOUNT_EXCEPTION, callerPackage);
Slogf.e(LOG_TAG, "Exception copying account to " + targetUser, e);
}
+ return false;
}
private static void logCopyAccountStatus(@CopyAccountStatus int status, String callerPackage) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index dd8757236a36..54de64e2f3a8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -22,7 +22,9 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
@@ -36,6 +38,9 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.PowerManager;
@@ -47,8 +52,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.Clock;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.testutils.OffsettableClock;
@@ -58,6 +61,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -65,18 +69,31 @@ import org.mockito.MockitoAnnotations;
public class AutomaticBrightnessControllerTest {
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
+ private static final int LIGHT_SENSOR_RATE = 20;
private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 2000;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
private static final float DOZE_SCALE_FACTOR = 0.54f;
+ private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
+ private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
+ private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
+ private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000;
private static final float EPSILON = 0.001f;
private OffsettableClock mClock = new OffsettableClock();
private TestLooper mTestLooper;
private Context mContext;
private AutomaticBrightnessController mController;
+ private Sensor mLightSensor;
+ @Mock SensorManager mSensorManager;
@Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
+ @Mock HysteresisLevels mAmbientBrightnessThresholds;
@Mock HysteresisLevels mScreenBrightnessThresholds;
+ @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
@Mock BrightnessRangeController mBrightnessRangeController;
@@ -84,18 +101,17 @@ public class AutomaticBrightnessControllerTest {
BrightnessClamperController mBrightnessClamperController;
@Mock BrightnessThrottler mBrightnessThrottler;
- @Mock
- LightSensorController mLightSensorController;
-
@Before
public void setUp() throws Exception {
// Share classloader to allow package private access.
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mContext = InstrumentationRegistry.getContext();
setupController(BrightnessMappingStrategy.INVALID_LUX,
- BrightnessMappingStrategy.INVALID_NITS);
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ false,
+ /* useHorizon= */ true);
}
@After
@@ -107,7 +123,8 @@ public class AutomaticBrightnessControllerTest {
}
}
- private void setupController(float userLux, float userNits) {
+ private void setupController(float userLux, float userNits, boolean applyDebounce,
+ boolean useHorizon) {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -130,22 +147,25 @@ public class AutomaticBrightnessControllerTest {
}
@Override
- Clock createClock() {
- return new Clock() {
- @Override
- public long uptimeMillis() {
- return mClock.now();
- }
- };
+ AutomaticBrightnessController.Clock createClock() {
+ return mClock::now;
}
}, // pass in test looper instead, pass in offsettable clock
- () -> { }, mTestLooper.getLooper(),
- brightnessMappingStrategyMap, BRIGHTNESS_MIN_FLOAT,
- BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, mScreenBrightnessThresholds,
- mScreenBrightnessThresholdsIdle,
+ () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor,
+ brightnessMappingStrategyMap, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
+ BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
+ INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0,
+ applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0,
+ applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0,
+ applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0,
+ RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
+ mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
+ mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mBrightnessRangeController, mBrightnessThrottler,
- userLux, userNits, mLightSensorController, mBrightnessClamperController
+ useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
+ useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
+ mBrightnessClamperController
);
when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
@@ -166,15 +186,20 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testNoHysteresisAtMinBrightness() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.02f as a brightness value
float lux1 = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness1 = 0.02f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
.thenReturn(normalizedBrightness1);
@@ -185,31 +210,39 @@ public class AutomaticBrightnessControllerTest {
.thenReturn(1.0f);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
float normalizedBrightness2 = 0.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
.thenReturn(normalizedBrightness2);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
public void testNoHysteresisAtMaxBrightness() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.98f as a brightness value
float lux1 = 100.0f;
float normalizedBrightness1 = 0.98f;
-
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
.thenReturn(normalizedBrightness1);
@@ -220,30 +253,35 @@ public class AutomaticBrightnessControllerTest {
.thenReturn(1.1f);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 1.0f as a brightness value (brightness_max)
float lux2 = 110.0f;
float normalizedBrightness2 = 1.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
.thenReturn(normalizedBrightness2);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
public void testUserAddUserDataPoint() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
@@ -260,11 +298,12 @@ public class AutomaticBrightnessControllerTest {
public void testRecalculateSplines() throws Exception {
// Enabling the light sensor, and setting the ambient lux to 1000
int currentLux = 1000;
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
- listener.onAmbientLuxChange(currentLux);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux));
// User sets brightness to 0.5f
when(mBrightnessMappingStrategy.getBrightness(currentLux,
@@ -294,13 +333,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testShortTermModelTimesOut() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -314,7 +354,7 @@ public class AutomaticBrightnessControllerTest {
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
mTestLooper.moveTimeForward(
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
@@ -333,13 +373,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testShortTermModelDoesntTimeOut() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
@@ -358,7 +399,7 @@ public class AutomaticBrightnessControllerTest {
mTestLooper.dispatchAll();
// Sensor reads 100000 lux,
- listener.onAmbientLuxChange(678910);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
// Verify short term model is not reset.
@@ -372,13 +413,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -398,7 +440,7 @@ public class AutomaticBrightnessControllerTest {
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
mTestLooper.moveTimeForward(
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
@@ -417,13 +459,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -445,7 +488,7 @@ public class AutomaticBrightnessControllerTest {
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// Do not fast-forward time.
mTestLooper.dispatchAll();
@@ -463,13 +506,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testSwitchBetweenModesNoUserInteractions() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
@@ -485,7 +529,7 @@ public class AutomaticBrightnessControllerTest {
BrightnessMappingStrategy.INVALID_LUX);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// Do not fast-forward time.
mTestLooper.dispatchAll();
@@ -501,19 +545,14 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testSwitchToIdleMappingStrategy() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
- clearInvocations(mBrightnessMappingStrategy);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
-
-
- verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt());
-
- clearInvocations(mBrightnessMappingStrategy);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
@@ -522,19 +561,22 @@ public class AutomaticBrightnessControllerTest {
/* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
- verify(mBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f,
+ verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
/* brightness= */ 0.5f);
- verify(mBrightnessMappingStrategy).setBrightnessConfiguration(any());
- verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt());
+ verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
+ verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
- clearInvocations(mBrightnessMappingStrategy);
// Now let's do the same for idle mode
mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
- verify(mBrightnessMappingStrategy).getMode();
- verify(mBrightnessMappingStrategy).getShortTermModelTimeout();
- verify(mBrightnessMappingStrategy).getUserBrightness();
- verify(mBrightnessMappingStrategy).getUserLux();
+ // Called once when switching,
+ // setAmbientLux() is called twice and once in updateAutoBrightness(),
+ // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are
+ // called twice each.
+ verify(mBrightnessMappingStrategy, times(8)).getMode();
+ // Called when switching.
+ verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
+ verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
+ verify(mBrightnessMappingStrategy, times(1)).getUserLux();
// Ensure, after switching, original BMS is not used anymore
verifyNoMoreInteractions(mBrightnessMappingStrategy);
@@ -546,25 +588,154 @@ public class AutomaticBrightnessControllerTest {
/* shouldResetShortTermModel= */ true);
// Ensure we use the correct mapping strategy
- verify(mIdleBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f,
+ verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
/* brightness= */ 0.5f);
}
@Test
+ public void testAmbientLightHorizon() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ long increment = 500;
+ // set autobrightness to low
+ // t = 0
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+ // t = 500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+ // t = 1000
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 2000
+ // ensure that our reading is at 0.
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // first 10000 lux sensor event reading
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 3000
+ // lux reading should still not yet be 10000.
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 3500
+ mClock.fastForward(increment);
+ // lux has been high (10000) for 1000ms.
+ // lux reading should be 10000
+ // short horizon (ambient lux) is high, long horizon is still not high
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 4000
+ // stay high
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 4500
+ Mockito.clearInvocations(mBrightnessMappingStrategy);
+ mClock.fastForward(increment);
+ // short horizon is high, long horizon is high too
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 5000
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 5500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 6000
+ mClock.fastForward(increment);
+ // ambient lux goes to 0
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // only the values within the horizon should be kept
+ assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+ EPSILON);
+ assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+ mController.getLastSensorTimestamps());
+ }
+
+ @Test
+ public void testHysteresisLevels() {
+ float[] ambientBrighteningThresholds = {50, 100};
+ float[] ambientDarkeningThresholds = {10, 20};
+ float[] ambientThresholdLevels = {0, 500};
+ float ambientDarkeningMinChangeThreshold = 3.0f;
+ float ambientBrighteningMinChangeThreshold = 1.5f;
+ HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+ ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
+ ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+ // test low, activate minimum change thresholds.
+ assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+ assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+ assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+ // test max
+ // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+ assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+ assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+ // test just below threshold
+ assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+ // test at (considered above) threshold
+ assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ }
+
+ @Test
public void testBrightnessGetsThrottled() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return max brightness at 100 lux
final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
final float lux = 100.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+ .thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+ .thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
.thenReturn(normalizedBrightness);
// Sensor reads 100 lux. We should get max brightness.
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
@@ -592,6 +763,94 @@ public class AutomaticBrightnessControllerTest {
}
@Test
+ public void testGetSensorReadings() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+ int increment = 11;
+ int lux = 5000;
+ for (int i = 0; i < 1000; i++) {
+ lux += increment;
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // Only the values within the horizon should be kept
+ assertEquals(valuesCount, sensorValues.length);
+ assertEquals(valuesCount, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = valuesCount - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ lux -= increment;
+ sensorTimestamp -= increment;
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
+ public void testGetSensorReadingsFullBuffer() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ int initialCapacity = 150;
+
+ // Choose values such that the ring buffer is pruned
+ int increment1 = 200;
+ int lux = 5000;
+ for (int i = 0; i < 20; i++) {
+ lux += increment1;
+ mClock.fastForward(increment1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+ // Choose values such that the buffer becomes full
+ int increment2 = 1;
+ for (int i = 0; i < initialCapacity - valuesCount; i++) {
+ lux += increment2;
+ mClock.fastForward(increment2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // The buffer should be full
+ assertEquals(initialCapacity, sensorValues.length);
+ assertEquals(initialCapacity, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = initialCapacity - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+ if (i >= valuesCount) {
+ lux -= increment2;
+ sensorTimestamp -= increment2;
+ } else {
+ lux -= increment1;
+ sensorTimestamp -= increment1;
+ }
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
public void testResetShortTermModelWhenConfigChanges() {
when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
@@ -616,22 +875,179 @@ public class AutomaticBrightnessControllerTest {
float userNits = 500;
float userBrightness = 0.3f;
when(mBrightnessMappingStrategy.getBrightnessFromNits(userNits)).thenReturn(userBrightness);
- setupController(userLux, userNits);
+ setupController(userLux, userNits, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
}
@Test
+ public void testBrighteningLightDebounce() throws Exception {
+ clearInvocations(mSensorManager);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1000
+ // Lux isn't steady yet
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux is steady now
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testDarkeningLightDebounce() throws Exception {
+ clearInvocations(mSensorManager);
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2000
+ // Lux isn't steady yet
+ mClock.fastForward(2000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 4500
+ // Lux is steady now
+ mClock.fastForward(2000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testBrighteningLightDebounceIdle() throws Exception {
+ clearInvocations(mSensorManager);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ // Lux is steady now
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testDarkeningLightDebounceIdle() throws Exception {
+ clearInvocations(mSensorManager);
+ when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 1000
+ // Lux isn't steady yet
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux is steady now
+ mClock.fastForward(1500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
public void testBrightnessBasedOnLastObservedLux() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
// Send a new sensor value, disable the sensor and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null,
/* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
/* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
@@ -643,19 +1059,21 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testAutoBrightnessInDoze() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Set policy to DOZE
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
@@ -664,7 +1082,7 @@ public class AutomaticBrightnessControllerTest {
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should be scaled by the doze factor
assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
@@ -677,19 +1095,21 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Switch mode to DOZE
mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
@@ -701,7 +1121,7 @@ public class AutomaticBrightnessControllerTest {
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
@@ -713,19 +1133,21 @@ public class AutomaticBrightnessControllerTest {
@Test
public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Set policy to DOZE
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
@@ -734,7 +1156,7 @@ public class AutomaticBrightnessControllerTest {
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index db2a1f46a891..afb87d1df798 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
@@ -78,8 +79,6 @@ import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.LightSensorController;
-import com.android.server.display.brightness.TestUtilsKt;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
@@ -1167,19 +1166,30 @@ public final class DisplayPowerControllerTest {
any(AutomaticBrightnessController.Callbacks.class),
any(Looper.class),
eq(mSensorManagerMock),
+ /* lightSensor= */ any(),
/* brightnessMappingStrategyMap= */ any(SparseArray.class),
+ /* lightSensorWarmUpTime= */ anyInt(),
/* brightnessMin= */ anyFloat(),
/* brightnessMax= */ anyFloat(),
/* dozeScaleFactor */ anyFloat(),
+ /* lightSensorRate= */ anyInt(),
+ /* initialLightSensorRate= */ anyInt(),
+ /* brighteningLightDebounceConfig */ anyLong(),
+ /* darkeningLightDebounceConfig */ anyLong(),
+ /* brighteningLightDebounceConfigIdle= */ anyLong(),
+ /* darkeningLightDebounceConfigIdle= */ anyLong(),
+ /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
+ /* ambientLightHorizonShort= */ anyInt(),
+ /* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(nits),
- eq(DISPLAY_ID),
- any(LightSensorController.LightSensorControllerConfig.class),
any(BrightnessClamperController.class)
);
}
@@ -2148,22 +2158,22 @@ public final class DisplayPowerControllerTest {
}
@Override
- LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig(
- Context context, DisplayDeviceConfig displayDeviceConfig) {
- return TestUtilsKt.createLightSensorControllerConfig();
- }
-
- @Override
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessRangeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
return mAutomaticBrightnessController;
}
@@ -2176,12 +2186,18 @@ public final class DisplayPowerControllerTest {
}
@Override
- HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) {
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
return mHysteresisLevels;
}
@Override
- HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) {
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
return mHysteresisLevels;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt b/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt
deleted file mode 100644
index 02d6946aa30c..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt
+++ /dev/null
@@ -1,57 +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.server.display
-
-import androidx.test.filters.SmallTest
-import com.android.server.display.brightness.createHysteresisLevels
-import kotlin.test.assertEquals
-import org.junit.Test
-
-private const val FLOAT_TOLERANCE = 0.001f
-@SmallTest
-class HysteresisLevelsTest {
- @Test
- fun `test hysteresis levels`() {
- val hysteresisLevels = createHysteresisLevels(
- brighteningThresholdsPercentages = floatArrayOf(50f, 100f),
- darkeningThresholdsPercentages = floatArrayOf(10f, 20f),
- brighteningThresholdLevels = floatArrayOf(0f, 500f),
- darkeningThresholdLevels = floatArrayOf(0f, 500f),
- minDarkeningThreshold = 3f,
- minBrighteningThreshold = 1.5f
- )
-
- // test low, activate minimum change thresholds.
- assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), FLOAT_TOLERANCE)
- assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), FLOAT_TOLERANCE)
- assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), FLOAT_TOLERANCE)
-
- // test max
- // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
- assertEquals(
- 20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), FLOAT_TOLERANCE * 2)
- assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), FLOAT_TOLERANCE)
-
- // test just below threshold
- assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), FLOAT_TOLERANCE)
- assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), FLOAT_TOLERANCE)
-
- // test at (considered above) threshold
- assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), FLOAT_TOLERANCE)
- assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), FLOAT_TOLERANCE)
- }
-} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt
deleted file mode 100644
index 5fe91786eb24..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt
+++ /dev/null
@@ -1,130 +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.server.display.brightness
-
-import androidx.test.filters.SmallTest
-import com.android.internal.os.Clock
-import com.android.server.display.brightness.LightSensorController.AmbientLightRingBuffer
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
-import org.junit.Test
-import org.mockito.kotlin.mock
-
-
-private const val BUFFER_INITIAL_CAPACITY = 3
-
-@SmallTest
-class AmbientLightRingBufferTest {
-
- private val buffer = AmbientLightRingBuffer(BUFFER_INITIAL_CAPACITY, mock<Clock>())
-
- @Test
- fun `test created empty`() {
- assertThat(buffer.size()).isEqualTo(0)
- }
-
- @Test
- fun `test push to empty buffer`() {
- buffer.push(1000, 0.5f)
-
- assertThat(buffer.size()).isEqualTo(1)
- assertThat(buffer.getLux(0)).isEqualTo(0.5f)
- assertThat(buffer.getTime(0)).isEqualTo(1000)
- }
-
- @Test
- fun `test prune keeps youngest outside horizon and sets time to horizon`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.prune(2500)
-
- assertThat(buffer.size()).isEqualTo(2)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.6f)
- assertThat(buffer.getTime(0)).isEqualTo(2500)
- }
-
- @Test
- fun `test prune keeps inside horizon`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.prune(2500)
-
- assertThat(buffer.size()).isEqualTo(2)
-
- assertThat(buffer.getLux(1)).isEqualTo(0.7f)
- assertThat(buffer.getTime(1)).isEqualTo(3000)
- }
-
-
- @Test
- fun `test pushes correctly after prune`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
- buffer.prune(2500)
-
- buffer.push(4000, 0.8f)
-
- assertThat(buffer.size()).isEqualTo(3)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.6f)
- assertThat(buffer.getTime(0)).isEqualTo(2500)
- assertThat(buffer.getLux(1)).isEqualTo(0.7f)
- assertThat(buffer.getTime(1)).isEqualTo(3000)
- assertThat(buffer.getLux(2)).isEqualTo(0.8f)
- assertThat(buffer.getTime(2)).isEqualTo(4000)
- }
-
- @Test
- fun `test increase buffer size`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.push(4000, 0.8f)
-
- assertThat(buffer.size()).isEqualTo(4)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.5f)
- assertThat(buffer.getTime(0)).isEqualTo(1000)
- assertThat(buffer.getLux(1)).isEqualTo(0.6f)
- assertThat(buffer.getTime(1)).isEqualTo(2000)
- assertThat(buffer.getLux(2)).isEqualTo(0.7f)
- assertThat(buffer.getTime(2)).isEqualTo(3000)
- assertThat(buffer.getLux(3)).isEqualTo(0.8f)
- assertThat(buffer.getTime(3)).isEqualTo(4000)
- }
-
- @Test
- fun `test buffer clear`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.clear()
-
- assertThat(buffer.size()).isEqualTo(0)
- assertThrows(ArrayIndexOutOfBoundsException::class.java) {
- buffer.getLux(0)
- }
- }
-} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt
deleted file mode 100644
index 966134aa2b77..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt
+++ /dev/null
@@ -1,432 +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.server.display.brightness
-
-import android.hardware.Sensor
-import android.hardware.SensorEvent
-import android.hardware.SensorEventListener
-import android.os.Handler
-import androidx.test.filters.SmallTest
-import com.android.internal.os.Clock
-import com.android.server.display.TestUtils
-import com.android.server.display.brightness.LightSensorController.Injector
-import com.android.server.display.brightness.LightSensorController.LightSensorControllerConfig
-import com.android.server.testutils.OffsettableClock
-import com.android.server.testutils.TestHandler
-import com.google.common.truth.Truth.assertThat
-import kotlin.math.ceil
-import kotlin.test.assertEquals
-import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertTrue
-import org.junit.Test
-
-private const val FLOAT_TOLERANCE = 0.001f
-
-@SmallTest
-class LightSensorControllerTest {
-
- private val testHandler = TestHandler(null)
- private val testInjector = TestInjector()
-
- @Test
- fun `test ambient light horizon`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfig = 0,
- darkeningLightDebounceConfig = 0,
- ambientLightHorizonShort = 1000,
- ambientLightHorizonLong = 2000
- ), testInjector, testHandler)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- val timeIncrement = 500L
- // set ambient lux to low
- // t = 0
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
-
- // t = 500
- //
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
-
- // t = 1000
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 1500
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 2000
- // ensure that our reading is at 0.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 2500
- // first 10000 lux sensor event reading
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 3000
- // lux reading should still not yet be 10000.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 3500
- testInjector.clock.fastForward(timeIncrement)
- // at short horizon, first value outside will be used in calculation (t = 2000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 4000
- // lux has been high (10000) for more than 1000ms.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 4500
- testInjector.clock.fastForward(timeIncrement)
- // short horizon is high, long horizon is high too
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 5000
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 5500
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 6000
- testInjector.clock.fastForward(timeIncrement)
- // at short horizon, first value outside will be used in calculation (t = 4500)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 6500
- testInjector.clock.fastForward(timeIncrement)
- // ambient lux goes to 0
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // only the values within the horizon should be kept
- assertArrayEquals(floatArrayOf(10_000f, 0f, 0f, 0f, 0f),
- lightSensorController.lastSensorValues, FLOAT_TOLERANCE)
- assertArrayEquals(longArrayOf(4_500, 5_000, 5_500, 6_000, 6_500),
- lightSensorController.lastSensorTimestamps)
- }
-
- @Test
- fun `test brightening debounce`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfig = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // brightening threshold is set to previous lux value
- ambientBrightnessThresholds = createHysteresisLevels(
- brighteningThresholdLevels = floatArrayOf(),
- brighteningThresholdsPercentages = floatArrayOf(),
- minBrighteningThreshold = 0f
- )
- ), testInjector, testHandler)
- lightSensorController.setIdleMode(false)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial brightening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux increase, first brightening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < brighteningLightDebounceConfig)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < brighteningLightDebounceConfig)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f))
- assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test sensor readings`() {
- val ambientLightHorizonLong = 2_500
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- ambientLightHorizonLong = ambientLightHorizonLong
- ), testInjector, testHandler)
- lightSensorController.setListener { }
- lightSensorController.setIdleMode(false)
- lightSensorController.enableLightSensorIfNeeded()
-
- // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
- val increment = 11
- var lux = 5000
- for (i in 0 until 1000) {
- lux += increment
- testInjector.clock.fastForward(increment.toLong())
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(lux.toFloat()))
- }
-
- val valuesCount = ceil(ambientLightHorizonLong.toDouble() / increment + 1).toInt()
- val sensorValues = lightSensorController.lastSensorValues
- val sensorTimestamps = lightSensorController.lastSensorTimestamps
-
- // Only the values within the horizon should be kept
- assertEquals(valuesCount, sensorValues.size)
- assertEquals(valuesCount, sensorTimestamps.size)
-
- var sensorTimestamp = testInjector.clock.now()
- for (i in valuesCount - 1 downTo 1) {
- assertEquals(lux.toFloat(), sensorValues[i], FLOAT_TOLERANCE)
- assertEquals(sensorTimestamp, sensorTimestamps[i])
- lux -= increment
- sensorTimestamp -= increment
- }
- assertEquals(lux.toFloat(), sensorValues[0], FLOAT_TOLERANCE)
- assertEquals(testInjector.clock.now() - ambientLightHorizonLong, sensorTimestamps[0])
- }
-
- @Test
- fun `test darkening debounce`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- darkeningLightDebounceConfig = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // darkening threshold is set to previous lux value
- ambientBrightnessThresholds = createHysteresisLevels(
- darkeningThresholdLevels = floatArrayOf(),
- darkeningThresholdsPercentages = floatArrayOf(),
- minDarkeningThreshold = 0f
- )
- ), testInjector, testHandler)
-
- lightSensorController.setIdleMode(false)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial darkening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux decreased, first darkening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < darkeningLightDebounceConfig)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < darkeningLightDebounceConfig)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f))
- assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test brightening debounce in idle mode`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfigIdle = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // brightening threshold is set to previous lux value
- ambientBrightnessThresholdsIdle = createHysteresisLevels(
- brighteningThresholdLevels = floatArrayOf(),
- brighteningThresholdsPercentages = floatArrayOf(),
- minBrighteningThreshold = 0f
- )
- ), testInjector, testHandler)
- lightSensorController.setIdleMode(true)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial brightening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux increase, first brightening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < brighteningLightDebounceConfigIdle)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < brighteningLightDebounceConfigIdle)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f))
- assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test darkening debounce in idle mode`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- darkeningLightDebounceConfigIdle = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // darkening threshold is set to previous lux value
- ambientBrightnessThresholdsIdle = createHysteresisLevels(
- darkeningThresholdLevels = floatArrayOf(),
- darkeningThresholdsPercentages = floatArrayOf(),
- minDarkeningThreshold = 0f
- )
- ), testInjector, testHandler)
-
- lightSensorController.setIdleMode(true)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial darkening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux decreased, first darkening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < darkeningLightDebounceConfigIdle)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < darkeningLightDebounceConfigIdle)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f))
- assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
-
- private fun sensorEvent(value: Float) = SensorEvent(
- testInjector.testSensor, 0, 0, floatArrayOf(value)
- )
-
- private class TestInjector : Injector {
- val testSensor: Sensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor")
- val clock: OffsettableClock = OffsettableClock.Stopped()
-
- var sensorEventListener: SensorEventListener? = null
- override fun getClock(): Clock {
- return object : Clock() {
- override fun uptimeMillis(): Long {
- return clock.now()
- }
- }
- }
-
- override fun getLightSensor(config: LightSensorControllerConfig): Sensor {
- return testSensor
- }
-
- override fun registerLightSensorListener(
- listener: SensorEventListener,
- sensor: Sensor,
- rate: Int,
- handler: Handler
- ): Boolean {
- sensorEventListener = listener
- return true
- }
-
- override fun unregisterLightSensorListener(listener: SensorEventListener) {
- sensorEventListener = null
- }
-
- override fun getTag(): String {
- return "LightSensorControllerTest"
- }
- }
-} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt
deleted file mode 100644
index 1328f5fa0e90..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt
+++ /dev/null
@@ -1,71 +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.server.display.brightness
-
-import com.android.server.display.HysteresisLevels
-import com.android.server.display.config.SensorData
-
-@JvmOverloads
-fun createLightSensorControllerConfig(
- initialSensorRate: Int = 1,
- normalSensorRate: Int = 2,
- resetAmbientLuxAfterWarmUpConfig: Boolean = true,
- ambientLightHorizonShort: Int = 1,
- ambientLightHorizonLong: Int = 10_000,
- lightSensorWarmUpTimeConfig: Int = 0,
- weightingIntercept: Int = 10_000,
- ambientBrightnessThresholds: HysteresisLevels = createHysteresisLevels(),
- ambientBrightnessThresholdsIdle: HysteresisLevels = createHysteresisLevels(),
- brighteningLightDebounceConfig: Long = 100_000,
- darkeningLightDebounceConfig: Long = 100_000,
- brighteningLightDebounceConfigIdle: Long = 100_000,
- darkeningLightDebounceConfigIdle: Long = 100_000,
- ambientLightSensor: SensorData = SensorData()
-) = LightSensorController.LightSensorControllerConfig(
- initialSensorRate,
- normalSensorRate,
- resetAmbientLuxAfterWarmUpConfig,
- ambientLightHorizonShort,
- ambientLightHorizonLong,
- lightSensorWarmUpTimeConfig,
- weightingIntercept,
- ambientBrightnessThresholds,
- ambientBrightnessThresholdsIdle,
- brighteningLightDebounceConfig,
- darkeningLightDebounceConfig,
- brighteningLightDebounceConfigIdle,
- darkeningLightDebounceConfigIdle,
- ambientLightSensor
-)
-
-fun createHysteresisLevels(
- brighteningThresholdsPercentages: FloatArray = floatArrayOf(),
- darkeningThresholdsPercentages: FloatArray = floatArrayOf(),
- brighteningThresholdLevels: FloatArray = floatArrayOf(),
- darkeningThresholdLevels: FloatArray = floatArrayOf(),
- minDarkeningThreshold: Float = 0f,
- minBrighteningThreshold: Float = 0f,
- potentialOldBrightnessRange: Boolean = false
-) = HysteresisLevels(
- brighteningThresholdsPercentages,
- darkeningThresholdsPercentages,
- brighteningThresholdLevels,
- darkeningThresholdLevels,
- minDarkeningThreshold,
- minBrighteningThreshold,
- potentialOldBrightnessRange
-) \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
index 344e2c21f0a5..69a98ace1c33 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.BrailleDisplayController;
+import android.accessibilityservice.IBrailleDisplayController;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -174,6 +175,17 @@ public class BrailleDisplayConnectionTest {
}
@Test
+ public void defaultNativeScanner_getName_returnsName() {
+ String name = "My Braille Display";
+ when(mNativeInterface.getHidrawName(anyInt())).thenReturn(name);
+
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+
+ assertThat(scanner.getName(NULL_PATH)).isEqualTo(name);
+ }
+
+ @Test
public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() {
Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
mBrailleDisplayConnection.write(
@@ -201,6 +213,38 @@ public class BrailleDisplayConnectionTest {
verify(mBrailleDisplayConnection).disconnect();
}
+ @Test
+ public void connect_unableToGetUniq_usesNameFallback() throws Exception {
+ try {
+ IBrailleDisplayController controller =
+ Mockito.mock(IBrailleDisplayController.class);
+ final Path path = Path.of("/dev/null");
+ final String macAddress = "00:11:22:33:AA:BB";
+ final String name = "My Braille Display";
+ final byte[] descriptor = {0x05, 0x41};
+ Bundle bd = new Bundle();
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH,
+ path.toString());
+ bd.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR,
+ descriptor);
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name);
+ bd.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, true);
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, null);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.setTestData(List.of(bd));
+ // Validate that the test data is set up correctly before attempting connection:
+ assertThat(scanner.getUniqueId(path)).isNull();
+ assertThat(scanner.getName(path)).isEqualTo(name);
+
+ mBrailleDisplayConnection.connectLocked(
+ macAddress, name, BrailleDisplayConnection.BUS_BLUETOOTH, controller);
+
+ verify(controller).onConnected(eq(mBrailleDisplayConnection), eq(descriptor));
+ } finally {
+ mBrailleDisplayConnection.disconnect();
+ }
+ }
+
// BrailleDisplayConnection#setTestData() is used to enable CTS testing with
// test Braille display data, but its own implementation should also be tested
// so that issues in this helper don't cause confusing failures in CTS.
@@ -220,6 +264,9 @@ public class BrailleDisplayConnectionTest {
String uniq1 = "uniq1", uniq2 = "uniq2";
bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1);
bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2);
+ String name1 = "name1", name2 = "name2";
+ bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name1);
+ bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name2);
int bus1 = BrailleDisplayConnection.BUS_USB, bus2 =
BrailleDisplayConnection.BUS_BLUETOOTH;
bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
@@ -235,6 +282,8 @@ public class BrailleDisplayConnectionTest {
expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2);
expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1);
expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2);
+ expect.that(scanner.getName(path1)).isEqualTo(name1);
+ expect.that(scanner.getName(path2)).isEqualTo(name2);
expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1);
expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 643dcec27bfd..0a2a855e9317 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -123,6 +123,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -678,6 +679,87 @@ public class UserControllerTest {
}
/**
+ * Test that, when exceeding the maximum number of running users, a profile of the current user
+ * is not stopped.
+ */
+ @Test
+ public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
+ throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+
+ final int PARENT_ID = 200;
+ final int PROFILE1_ID = 201;
+ final int PROFILE2_ID = 202;
+ final int FG_USER_ID = 300;
+ final int BG_USER_ID = 400;
+
+ setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE1_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE2_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(FG_USER_ID, 0).profileGroupId = FG_USER_ID;
+ setUpUser(BG_USER_ID, 0).profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ int numberOfUserSwitches = 1;
+ addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ assertTrue(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertThat(mUserController.startProfile(PROFILE1_ID, true, null)).isTrue();
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ numberOfUserSwitches++;
+ addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ assertTrue(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, BG_USER_ID, FG_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ // Now we exceed the maxRunningUsers parameter (of 5):
+ assertThat(mUserController.startProfile(PROFILE2_ID, true, null)).isTrue();
+ // Currently, starting a profile doesn't trigger evaluating whether we've exceeded max, so
+ // we expect no users to be stopped. This policy may change in the future. Log but no fail.
+ if (!newHashSet(SYSTEM_USER_ID, PROFILE1_ID, BG_USER_ID, PROFILE2_ID, PARENT_ID, FG_USER_ID)
+ .equals(new HashSet<>(mUserController.getRunningUsersLU()))) {
+ Log.w(TAG, "Starting a profile that exceeded max running users didn't lead to "
+ + "expectations: " + mUserController.getRunningUsersLU());
+ }
+
+ numberOfUserSwitches++;
+ addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ // We've now done a user switch and should notice that we've exceeded the maximum number of
+ // users. The oldest background user should be stopped (BG_USER); even though PROFILE1 was
+ // older, it should not be stopped since it's a profile of the (new) current user.
+ assertFalse(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PROFILE2_ID, FG_USER_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ }
+
+ /**
* Test that, in getRunningUsersLU, parents come after their profile, even if the profile was
* started afterwards.
*/
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 85172e08423e..856ad2a02444 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -928,6 +928,17 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testIsLetterboxed_activityFromBubble_returnsFalse() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity);
+ doReturn(true).when(mActivity).getLaunchedFromBubble();
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ }
+
+ @Test
public void testAspectRatioMatchParentBoundsAndImeAttachable() {
setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2000).build());
prepareUnresizable(mActivity, 2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
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 55a00fc0ec89..48fc2dc4bada 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -799,6 +799,7 @@ public class WindowContainerTests extends WindowTestsBase {
verify(child).onConfigurationChanged(any());
}
+ @SuppressWarnings("SelfComparison")
@Test
public void testCompareTo() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index df32fbd2e58d..ed3d5d546f72 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9854,6 +9854,19 @@ public class CarrierConfigManager {
"satellite_entitlement_supported_bool";
/**
+ * Indicates the appName that is used when querying the entitlement server for satellite.
+ *
+ * The default value is androidSatmode.
+ *
+ * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management.
+ * `app_name` is an optional attribute in the request and may vary depending on the carrier
+ * requirement.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING =
+ "satellite_entitlement_app_name_string";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10997,6 +11010,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true);
sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+ sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);