summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp3
-rw-r--r--core/api/current.txt5
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/java/android/app/Activity.java49
-rw-r--r--core/java/android/app/ActivityThread.java8
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/app/Notification.java14
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java2
-rw-r--r--core/java/android/app/compat/ChangeIdStateCache.java16
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java23
-rw-r--r--core/java/android/content/Context.java59
-rw-r--r--core/java/android/content/ContextWrapper.java6
-rw-r--r--core/java/android/content/pm/PackageParser.java4
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpoint.aidl6
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig9
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java60
-rw-r--r--core/java/android/os/Debug.java42
-rw-r--r--core/java/android/os/UserManager.java22
-rw-r--r--core/java/android/permission/IPermissionManager.aidl2
-rw-r--r--core/java/android/permission/PermissionManager.java70
-rw-r--r--core/java/android/permission/flags.aconfig9
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/autofill/augmented/FillWindow.java8
-rw-r--r--core/java/android/view/ViewRootImpl.java6
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java4
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java25
-rw-r--r--core/java/com/android/server/pm/pkg/AndroidPackage.java2
-rw-r--r--core/jni/android_view_SurfaceControlActivePictureListener.cpp5
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/layout/notification_2025_template_header.xml1
-rw-r--r--core/res/res/values/attrs_manifest.xml11
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/config_telephony.xml6
-rw-r--r--core/res/res/values/public-staging.xml5
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java2
-rw-r--r--core/tests/coretests/src/android/os/IpcDataCacheTest.java2
-rw-r--r--data/etc/privapp-permissions-platform.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_header_maximize_menu_button_progress_indicator_layout.xml34
-rw-r--r--libs/WindowManager/Shell/res/layout/maximize_menu_button.xml19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt59
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt24
-rw-r--r--media/java/android/media/MediaFormat.java18
-rw-r--r--media/java/android/media/MediaRoute2Info.java9
-rw-r--r--native/android/dynamic_instrumentation_manager.cpp3
-rw-r--r--native/android/include_platform/android/dynamic_instrumentation_manager.h46
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java5
-rw-r--r--packages/NeuralNetworks/framework/Android.bp17
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/DownloadCallback.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ICancellationSignal.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IDownloadCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IProcessingSignal.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IRemoteCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IResponseCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingCallback.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingSignal.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.aidl (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/utils/BinderUtils.java (renamed from packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java)0
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java (renamed from packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java)0
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/DownloadCallback.java113
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.aidl22
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.java267
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.aidl22
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.java178
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ICancellationSignal.aidl24
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IDownloadCallback.aidl31
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureCallback.aidl14
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl14
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl15
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl79
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IProcessingSignal.aidl14
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IRemoteCallback.aidl24
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IResponseCallback.aidl16
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl18
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl14
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.aidl22
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java220
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java200
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java54
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java642
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingCallback.java72
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingSignal.java225
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java41
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.aidl22
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.java94
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/utils/BinderUtils.java96
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl51
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl53
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl14
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl31
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl34
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java552
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java617
-rw-r--r--packages/NeuralNetworks/service/Android.bp13
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/BundleUtil.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java)0
-rw-r--r--packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java (renamed from packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java)0
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/BundleUtil.java407
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java101
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java40
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java1096
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java144
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java66
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java76
-rw-r--r--packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java97
-rw-r--r--packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java4
-rw-r--r--packages/SettingsLib/res/values/arrays.xml21
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java16
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java55
-rw-r--r--packages/Shell/tests/src/com/android/shell/UiBot.java26
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt46
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt29
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt16
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt50
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt19
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt123
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt55
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt32
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestReplaceOverlayTransition.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java227
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java278
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java273
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt157
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt111
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt9
-rw-r--r--packages/SystemUI/res/values/strings.xml11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/FontStyles.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt300
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/dagger/BrightnessSliderModule.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java199
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt2
-rw-r--r--services/Android.bp3
-rw-r--r--services/autofill/bugfixes.aconfig10
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java29
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java230
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java3
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java20
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java29
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java22
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java25
-rw-r--r--services/core/java/com/android/server/display/color/DisplayTransformManager.java6
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java17
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java18
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java11
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java66
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java137
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java26
-rw-r--r--services/core/java/com/android/server/pm/RestrictionsSet.java8
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java1
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java10
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java11
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java7
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java5
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java12
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java3
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java8
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java8
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java18
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java7
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_UsbAlsaDevice.cpp70
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt44
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java18
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java38
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java80
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java53
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java549
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java172
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java51
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java184
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java119
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaDevice.java6
-rw-r--r--services/usb/java/com/android/server/usb/flags/usb_flags.aconfig7
335 files changed, 10268 insertions, 2613 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 45e33ce4b6e9..97d28d1a6506 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -401,6 +401,7 @@ java_aconfig_library {
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.tethering",
"com.android.wifi",
],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
@@ -1215,6 +1216,7 @@ java_aconfig_library {
// DevicePolicy
aconfig_declarations {
name: "device_policy_aconfig_flags",
+ exportable: true,
package: "android.app.admin.flags",
container: "system",
srcs: [
@@ -1233,6 +1235,7 @@ java_aconfig_library {
aconfig_declarations: "device_policy_aconfig_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
min_sdk_version: "30",
+ mode: "exported",
apex_available: [
"//apex_available:platform",
"com.android.permission",
diff --git a/core/api/current.txt b/core/api/current.txt
index cfbf9e55c7dc..ca4b2fae2f99 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -98,6 +98,7 @@ package android {
field public static final String DUMP = "android.permission.DUMP";
field public static final String ENFORCE_UPDATE_OWNERSHIP = "android.permission.ENFORCE_UPDATE_OWNERSHIP";
field public static final String EXECUTE_APP_ACTION = "android.permission.EXECUTE_APP_ACTION";
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS = "android.permission.EXECUTE_APP_FUNCTIONS";
field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
@@ -8892,8 +8893,8 @@ package android.app.appfunctions {
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
+ method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e1d8fb11efb3..f0f0fc98881e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -150,7 +150,6 @@ package android {
field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
- field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS = "android.permission.EXECUTE_APP_FUNCTIONS";
field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS_TRUSTED = "android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -523,7 +522,6 @@ package android {
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public static final int config_defaultOnDeviceIntelligenceDeviceConfigNamespace;
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public static final int config_defaultOnDeviceIntelligenceService;
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public static final int config_defaultOnDeviceSandboxedInferenceService;
- field @FlaggedApi("android.permission.flags.cross_user_role_platform_api_enabled") public static final int config_defaultReservedForTestingProfileGroupExclusivity;
field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo = 17039432; // 0x1040048
field public static final int config_defaultSms = 17039396; // 0x1040024
field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet = 17039433; // 0x1040049
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 03ef669c0675..fee8cdb1ce51 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5782,8 +5782,7 @@ public class Activity extends ContextThemeWrapper
}
if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
- final int permissionCount = permissions.length;
- for (int i = 0; i < permissionCount; i++) {
+ for (int i = 0; i < permissions.length; i++) {
if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) {
throw new IllegalArgumentException("Cannot request renounced permission: "
+ permissions[i]);
@@ -5791,13 +5790,55 @@ public class Activity extends ContextThemeWrapper
}
}
- PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
- : createDeviceContext(deviceId).getPackageManager();
+ final Context context = getDeviceId() == deviceId ? this : createDeviceContext(deviceId);
+ if (Flags.permissionRequestShortCircuitEnabled()) {
+ int[] permissionsState = getPermissionRequestStates(context, permissions);
+ boolean hasRequestablePermission = false;
+ for (int i = 0; i < permissionsState.length; i++) {
+ if (permissionsState[i] == Context.PERMISSION_REQUEST_STATE_REQUESTABLE) {
+ hasRequestablePermission = true;
+ break;
+ }
+ }
+ // If none of the permissions is requestable, finish the request here.
+ if (!hasRequestablePermission) {
+ mHasCurrentPermissionsRequest = true;
+ Log.v(TAG, "No requestable permission in the request.");
+ int[] results = new int[permissionsState.length];
+ for (int i = 0; i < permissionsState.length; i++) {
+ if (permissionsState[i] == Context.PERMISSION_REQUEST_STATE_GRANTED) {
+ results[i] = PackageManager.PERMISSION_GRANTED;
+ } else {
+ results[i] = PackageManager.PERMISSION_DENIED;
+ }
+ }
+ // Currently permission request result is passed to the client app asynchronously
+ // in onRequestPermissionsResult, lets keep async behavior here as well.
+ mHandler.post(() -> {
+ mHasCurrentPermissionsRequest = false;
+ onRequestPermissionsResult(requestCode, permissions, results, deviceId);
+ });
+ return;
+ }
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
final Intent intent = packageManager.buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
+ @NonNull
+ private int[] getPermissionRequestStates(@NonNull Context deviceContext,
+ @NonNull String[] permissions) {
+ final int size = permissions.length;
+ int[] results = new int[size];
+ for (int i = 0; i < size; i++) {
+ results[i] = deviceContext.getPermissionRequestState(permissions[i]);
+ }
+ return results;
+ }
+
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {@link #requestPermissions}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ec173338f262..717a2acb4b4a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7098,12 +7098,9 @@ public final class ActivityThread extends ClientTransactionHandler
System.runFinalization();
System.gc();
}
- if (dhd.dumpBitmaps != null) {
- Bitmap.dumpAll(dhd.dumpBitmaps);
- }
try (ParcelFileDescriptor fd = dhd.fd) {
if (dhd.managed) {
- Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
+ Debug.dumpHprofData(dhd.path, fd.getFileDescriptor(), dhd.dumpBitmaps);
} else if (dhd.mallocInfo) {
Debug.dumpNativeMallocInfo(fd.getFileDescriptor());
} else {
@@ -7128,9 +7125,6 @@ public final class ActivityThread extends ClientTransactionHandler
if (dhd.finishCallback != null) {
dhd.finishCallback.sendResult(null);
}
- if (dhd.dumpBitmaps != null) {
- Bitmap.dumpAll(null); // clear dump
- }
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dcbdc2348fbc..d8aa8b3df622 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2366,7 +2366,11 @@ class ContextImpl extends Context {
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
+ int deviceId = resolveDeviceIdForPermissionCheck(permission);
+ return PermissionManager.checkPermission(permission, pid, uid, deviceId);
+ }
+ private int resolveDeviceIdForPermissionCheck(String permission) {
// When checking a device-aware permission on a remote device, if the permission is CAMERA
// or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
// device doesn't have capability fall back to checking permission on the default device.
@@ -2387,9 +2391,9 @@ class ContextImpl extends Context {
VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
if (virtualDevice != null) {
if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
- && !virtualDevice.hasCustomAudioInputSupport())
+ && !virtualDevice.hasCustomAudioInputSupport())
|| (Objects.equals(permission, Manifest.permission.CAMERA)
- && !virtualDevice.hasCustomCameraSupport())) {
+ && !virtualDevice.hasCustomCameraSupport())) {
deviceId = Context.DEVICE_ID_DEFAULT;
}
} else {
@@ -2400,8 +2404,7 @@ class ContextImpl extends Context {
}
}
}
-
- return PermissionManager.checkPermission(permission, pid, uid, deviceId);
+ return deviceId;
}
/** @hide */
@@ -2503,6 +2506,16 @@ class ContextImpl extends Context {
message);
}
+ /** @hide */
+ @Override
+ public int getPermissionRequestState(String permission) {
+ Objects.requireNonNull(permission, "Permission name can't be null");
+ int deviceId = resolveDeviceIdForPermissionCheck(permission);
+ PermissionManager permissionManager = getSystemService(PermissionManager.class);
+ return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
+ deviceId);
+ }
+
@Override
public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index aa2ada5372af..17638ee76dba 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3323,6 +3323,18 @@ public class Notification implements Parcelable
}
/**
+ * Make sure this String is safe to put into a bundle.
+ * @hide
+ */
+ public static String safeString(String str) {
+ if (str == null) return str;
+ if (str.length() > MAX_CHARSEQUENCE_LENGTH) {
+ str = str.substring(0, MAX_CHARSEQUENCE_LENGTH);
+ }
+ return str;
+ }
+
+ /**
* Make sure this CharSequence is safe to put into a bundle, which basically
* means it had better not be some custom Parcelable implementation.
* @hide
@@ -5051,7 +5063,7 @@ public class Notification implements Parcelable
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
@NonNull
public Builder setShortCriticalText(@Nullable String shortCriticalText) {
- mN.extras.putString(EXTRA_SHORT_CRITICAL_TEXT, shortCriticalText);
+ mN.extras.putString(EXTRA_SHORT_CRITICAL_TEXT, safeString(shortCriticalText));
return this;
}
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index c72c4c8feb71..5567c085d205 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1356,7 +1356,7 @@ public class PropertyInvalidatedCache<Query, Result> {
@Nullable QueryHandler<Query, Result> computer) {
mPropertyName = createPropertyName(args.mModule, args.mApi);
mCacheName = cacheName;
- mCacheNullResults = args.mCacheNulls && Flags.picCacheNulls();
+ mCacheNullResults = args.mCacheNulls;
mNonce = getNonceHandler(mPropertyName);
mMaxEntries = args.mMaxEntries;
mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode);
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index 7d21cbf955d9..258ce06bffc3 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -16,8 +16,6 @@
package android.app.compat;
-import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
-
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
import android.content.Context;
@@ -34,7 +32,10 @@ import com.android.internal.compat.IPlatformCompat;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
- private static final String CACHE_KEY = createSystemCacheKey("is_compat_change_enabled");
+
+ private static final String CACHE_MODULE = PropertyInvalidatedCache.MODULE_SYSTEM;
+ private static final String CACHE_API = "is_compat_change_enabled";
+
private static final int MAX_ENTRIES = 2048;
private static boolean sDisabled = getDefaultDisabled();
private volatile IPlatformCompat mPlatformCompat;
@@ -51,7 +52,12 @@ public final class ChangeIdStateCache
/** @hide */
public ChangeIdStateCache() {
- super(MAX_ENTRIES, CACHE_KEY);
+ super(new PropertyInvalidatedCache.Args(CACHE_MODULE)
+ .maxEntries(MAX_ENTRIES)
+ .isolateUids(false)
+ .cacheNulls(false)
+ .api(CACHE_API),
+ CACHE_API, null);
}
/**
@@ -72,7 +78,7 @@ public final class ChangeIdStateCache
*/
public static void invalidate() {
if (!sDisabled) {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
+ PropertyInvalidatedCache.invalidateCache(CACHE_MODULE, CACHE_API);
}
}
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index aee1cd9b4760..a5b58f968c27 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -16,8 +16,10 @@
package android.app.supervision;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.RemoteException;
@@ -32,9 +34,7 @@ public class SupervisionManager {
private final Context mContext;
private final ISupervisionManager mService;
- /**
- * @hide
- */
+ /** @hide */
@UnsupportedAppUsage
public SupervisionManager(Context context, ISupervisionManager service) {
mContext = context;
@@ -48,8 +48,23 @@ public class SupervisionManager {
*/
@UserHandleAware
public boolean isSupervisionEnabled() {
+ return isSupervisionEnabledForUser(mContext.getUserId());
+ }
+
+ /**
+ * Returns whether the device is supervised.
+ *
+ * <p>The caller must be from the same user as the target or hold the {@link
+ * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(
+ value = android.Manifest.permission.INTERACT_ACROSS_USERS,
+ conditional = true)
+ public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
try {
- return mService.isSupervisionEnabledForUser(mContext.getUserId());
+ return mService.isSupervisionEnabledForUser(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6ec6a62ff639..7abf5600d659 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -788,6 +788,40 @@ public abstract class Context {
public static final int RECEIVER_NOT_EXPORTED = 0x4;
/**
+ * The permission is granted.
+ *
+ * @hide
+ */
+ public static final int PERMISSION_REQUEST_STATE_GRANTED = 0;
+
+ /**
+ * The permission isn't granted, but apps can request the permission. When the app request
+ * the permission, user will be prompted with permission dialog to grant or deny the request.
+ *
+ * @hide
+ */
+ public static final int PERMISSION_REQUEST_STATE_REQUESTABLE = 1;
+
+ /**
+ * The permission is denied, and shouldn't be requested by apps. Permission request
+ * will be automatically denied by the system, preventing the permission dialog from being
+ * displayed to the user.
+ *
+ * @hide
+ */
+ public static final int PERMISSION_REQUEST_STATE_UNREQUESTABLE = 2;
+
+
+ /** @hide */
+ @IntDef(prefix = { "PERMISSION_REQUEST_STATE_" }, value = {
+ PERMISSION_REQUEST_STATE_GRANTED,
+ PERMISSION_REQUEST_STATE_REQUESTABLE,
+ PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionRequestState {}
+
+ /**
* Returns an AssetManager instance for the application's package.
* <p>
* <strong>Note:</strong> Implementations of this method should return
@@ -6989,6 +7023,31 @@ public abstract class Context {
@NonNull @PermissionName String permission, @Nullable String message);
/**
+ * Returns the permission request state for a given runtime permission. This method provides a
+ * streamlined mechanism for applications to determine whether a permission can be
+ * requested (i.e. whether the user will be prompted with a permission dialog).
+ *
+ * <p>Traditionally, determining if a permission has been permanently denied (unrequestable)
+ * required applications to initiate a permission request and subsequently analyze the result
+ * of {@link android.app.Activity#shouldShowRequestPermissionRationale} in conjunction with the
+ * grant result within the {@link android.app.Activity#onRequestPermissionsResult} callback.
+ *
+ * @param permission The name of the permission.
+ *
+ * @return The current request state of the specified permission, represented by one of the
+ * following constants: {@link PermissionRequestState#PERMISSION_REQUEST_STATE_GRANTED},
+ * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_REQUESTABLE}, or
+ * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_UNREQUESTABLE}.
+ *
+ * @hide
+ */
+ @CheckResult
+ @PermissionRequestState
+ public int getPermissionRequestState(@NonNull String permission) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Grant permission to access a specific Uri to another package, regardless
* of whether that package has general permission to access the Uri's
* content provider. This can be used to grant specific, temporary
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 413eb9886392..a146807b9fcf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1012,6 +1012,12 @@ public class ContextWrapper extends Context {
mBase.enforceCallingOrSelfPermission(permission, message);
}
+ /** @hide */
+ @Override
+ public int getPermissionRequestState(String permission) {
+ return mBase.getPermissionRequestState(permission);
+ }
+
@Override
public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
mBase.grantUriPermission(toPackage, uri, modeFlags);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4b579e7db9f8..219b20428d7a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2322,10 +2322,10 @@ public class PackageParser {
} else if (tagName.equals(TAG_ADOPT_PERMISSIONS)) {
sa = res.obtainAttributes(parser,
- com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+ com.android.internal.R.styleable.AndroidManifestAdoptPermissions);
String name = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
+ com.android.internal.R.styleable.AndroidManifestAdoptPermissions_name, 0);
sa.recycle();
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index b76b2271fe57..44f80c819e83 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -39,6 +39,7 @@ interface IContextHubEndpoint {
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
int openSession(in HubEndpointInfo destination, in @nullable String serviceDescriptor);
/**
@@ -49,6 +50,7 @@ interface IContextHubEndpoint {
*
* @throws IllegalStateException If the session wasn't opened.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
void closeSession(int sessionId, int reason);
/**
@@ -60,11 +62,13 @@ interface IContextHubEndpoint {
*
* @throws IllegalStateException If the session wasn't opened.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
void openSessionRequestComplete(int sessionId);
/**
* Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
void unregister();
/**
@@ -76,6 +80,7 @@ interface IContextHubEndpoint {
* @param transactionCallback Nullable. If the hub message requires a reply, the transactionCallback
* will be set to non-null.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessage(int sessionId, in HubMessage message,
in @nullable IContextHubTransactionCallback transactionCallback);
@@ -87,5 +92,6 @@ interface IContextHubEndpoint {
* @param messageSeqNumber The message sequence number, this should match a previously received HubMessage.
* @param errorCode The message delivery status detail.
*/
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode);
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index aaa78aa0916a..313bad50e88e 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -196,4 +196,11 @@ flag {
namespace: "input"
description: "Allows the user to disable pointer acceleration for mouse and touchpads."
bug: "349006858"
-} \ No newline at end of file
+}
+
+flag {
+ name: "mouse_scrolling_acceleration"
+ namespace: "input"
+ description: "Allows the user to disable input scrolling acceleration for mouse."
+ bug: "383555305"
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d9888ad6cd8d..1e0cc94612dd 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -754,6 +754,7 @@ public final class ContextHubManager {
* @param executor the executor to invoke callbacks for this client
* @return the callback interface
*/
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
IHubEndpointDiscoveryCallback callback,
Executor executor,
@@ -767,21 +768,9 @@ public final class ContextHubManager {
}
executor.execute(
() -> {
- // TODO(b/380293951): Refactor
List<HubDiscoveryInfo> discoveryList =
- new ArrayList<>(hubEndpointInfoList.length);
- for (HubEndpointInfo info : hubEndpointInfoList) {
- if (serviceDescriptor != null) {
- for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
- if (sInfo.getServiceDescriptor()
- .equals(serviceDescriptor)) {
- discoveryList.add(new HubDiscoveryInfo(info, sInfo));
- }
- }
- } else {
- discoveryList.add(new HubDiscoveryInfo(info));
- }
- }
+ getMatchingEndpointDiscoveryList(
+ hubEndpointInfoList, serviceDescriptor);
if (discoveryList.isEmpty()) {
Log.w(TAG, "onEndpointsStarted: no matching service descriptor");
} else {
@@ -799,19 +788,8 @@ public final class ContextHubManager {
executor.execute(
() -> {
List<HubDiscoveryInfo> discoveryList =
- new ArrayList<>(hubEndpointInfoList.length);
- for (HubEndpointInfo info : hubEndpointInfoList) {
- if (serviceDescriptor != null) {
- for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
- if (sInfo.getServiceDescriptor()
- .equals(serviceDescriptor)) {
- discoveryList.add(new HubDiscoveryInfo(info, sInfo));
- }
- }
- } else {
- discoveryList.add(new HubDiscoveryInfo(info));
- }
- }
+ getMatchingEndpointDiscoveryList(
+ hubEndpointInfoList, serviceDescriptor);
if (discoveryList.isEmpty()) {
Log.w(TAG, "onEndpointsStopped: no matching service descriptor");
} else {
@@ -823,6 +801,34 @@ public final class ContextHubManager {
}
/**
+ * Generates a list of matching endpoint discovery info, given the list and an (optional)
+ * service descriptor. If service descriptor is null, all endpoints are added to the filtered
+ * output list.
+ *
+ * @param hubEndpointInfoList The hub endpoints to filter.
+ * @param serviceDescriptor The optional service descriptor to match, null if adding all
+ * endpoints.
+ * @return The list of filtered HubDiscoveryInfo which matches the serviceDescriptor.
+ */
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ private List<HubDiscoveryInfo> getMatchingEndpointDiscoveryList(
+ HubEndpointInfo[] hubEndpointInfoList, @Nullable String serviceDescriptor) {
+ List<HubDiscoveryInfo> discoveryList = new ArrayList<>(hubEndpointInfoList.length);
+ for (HubEndpointInfo info : hubEndpointInfoList) {
+ if (serviceDescriptor != null) {
+ for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
+ if (sInfo.getServiceDescriptor().equals(serviceDescriptor)) {
+ discoveryList.add(new HubDiscoveryInfo(info, sInfo));
+ }
+ }
+ } else {
+ discoveryList.add(new HubDiscoveryInfo(info));
+ }
+ }
+ return discoveryList;
+ }
+
+ /**
* Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback,
* Executor)} with the default executor in the main thread.
*/
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index ef1e6c9405f3..2bc6ab5a18e9 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.graphics.Bitmap;
import android.util.Log;
import com.android.internal.util.FastPrintWriter;
@@ -2139,6 +2140,47 @@ public final class Debug
}
/**
+ * Like dumpHprofData(String), but takes an argument of bitmapFormat,
+ * which can be png, jpg, webp, or null (no bitmaps in heapdump).
+ *
+ * @hide
+ */
+ public static void dumpHprofData(String fileName, String bitmapFormat)
+ throws IOException {
+ try {
+ if (bitmapFormat != null) {
+ Bitmap.dumpAll(bitmapFormat);
+ }
+ VMDebug.dumpHprofData(fileName);
+ } finally {
+ if (bitmapFormat != null) {
+ Bitmap.dumpAll(null); // clear dump data
+ }
+ }
+ }
+
+ /**
+ * Like dumpHprofData(String, FileDescriptor), but takes an argument
+ * of bitmapFormat, which can be png, jpg, webp, or null (no bitmaps
+ * in heapdump).
+ *
+ * @hide
+ */
+ public static void dumpHprofData(String fileName, FileDescriptor fd,
+ String bitmapFormat) throws IOException {
+ try {
+ if (bitmapFormat != null) {
+ Bitmap.dumpAll(bitmapFormat);
+ }
+ VMDebug.dumpHprofData(fileName, fd);
+ } finally {
+ if (bitmapFormat != null) {
+ Bitmap.dumpAll(null); // clear dump data
+ }
+ }
+ }
+
+ /**
* Collect "hprof" and send it to DDMS. This may cause a GC.
*
* @throws UnsupportedOperationException if the VM was built without
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b9f2cfcd8ca8..132805da7c94 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4197,12 +4197,21 @@ public class UserManager {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
private boolean hasUserRestrictionForUser(@NonNull @UserRestrictionKey String restrictionKey,
- @UserIdInt int userId) {
- try {
- return mService.hasUserRestriction(restrictionKey, userId);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ @NonNull @UserIdInt int userId) {
+ return getUserRestrictionFromQuery(new Pair(restrictionKey, userId));
+ }
+
+ /** @hide */
+ @CachedProperty()
+ private boolean getUserRestrictionFromQuery(@NonNull Pair<String, Integer> restrictionPerUser) {
+ return UserManagerCache.getUserRestrictionFromQuery(
+ (Pair<String, Integer> q) -> mService.hasUserRestriction(q.first, q.second),
+ restrictionPerUser);
+ }
+
+ /** @hide */
+ public static final void invalidateUserRestriction() {
+ UserManagerCache.invalidateUserRestrictionFromQuery();
}
/**
@@ -6477,6 +6486,7 @@ public class UserManager {
UserManagerCache.invalidateProfileParent();
}
invalidateEnabledProfileIds();
+ invalidateUserRestriction();
}
/**
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 55011e52724c..b37581260bb1 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -108,6 +108,8 @@ interface IPermissionManager {
int checkUidPermission(int uid, String permissionName, int deviceId);
Map<String, PermissionState> getAllPermissionStates(String packageName, String persistentDeviceId, int userId);
+
+ int getPermissionRequestState(String packageName, String permissionName, int deviceId);
}
/**
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 2473de4ff6d7..bdf8d23438df 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1742,6 +1742,16 @@ public final class PermissionManager {
}
}
+ private static int getPermissionRequestStateUncached(String packageName, String permission,
+ int deviceId) {
+ try {
+ return AppGlobals.getPermissionManager().getPermissionRequestState(
+ packageName, permission, deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Identifies a permission query.
*
@@ -1795,6 +1805,46 @@ public final class PermissionManager {
}
}
+ private static final class PermissionRequestStateQuery {
+ final String mPackageName;
+ final String mPermission;
+ final int mDeviceId;
+
+ PermissionRequestStateQuery(@NonNull String packageName, @NonNull String permission,
+ int deviceId) {
+ mPackageName = packageName;
+ mPermission = permission;
+ mDeviceId = deviceId;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("PermissionRequestStateQuery(package=\"%s\","
+ + " permission=\"%s\", " + "deviceId=%d)",
+ mPackageName, mPermission, mDeviceId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mPermission, mDeviceId);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object rval) {
+ if (rval == null) {
+ return false;
+ }
+ PermissionRequestStateQuery other;
+ try {
+ other = (PermissionRequestStateQuery) rval;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+ return mDeviceId == other.mDeviceId && Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mPermission, other.mPermission);
+ }
+ }
+
// The legacy system property "package_info" had two purposes: to invalidate PIC caches and to
// signal that package information, and therefore permissions, might have changed.
// AudioSystem is the only client of the signaling behavior. The "separate permissions
@@ -1842,10 +1892,30 @@ public final class PermissionManager {
};
/** @hide */
+ private static final PropertyInvalidatedCache<PermissionRequestStateQuery, Integer>
+ sPermissionRequestStateCache =
+ new PropertyInvalidatedCache<>(
+ 512, CACHE_KEY_PACKAGE_INFO_CACHE, "getPermissionRequestState") {
+ @Override
+ public Integer recompute(PermissionRequestStateQuery query) {
+ return getPermissionRequestStateUncached(query.mPackageName, query.mPermission,
+ query.mDeviceId);
+ }
+ };
+
+ /** @hide */
public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) {
return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId));
}
+ /** @hide */
+ @Context.PermissionRequestState
+ public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission,
+ int deviceId) {
+ return sPermissionRequestStateCache.query(
+ new PermissionRequestStateQuery(packageName, permission, deviceId));
+ }
+
/**
* Gets the permission states for requested package and persistent device.
* <p>
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index af96ccfee787..07b9f5242b42 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -467,15 +467,6 @@ flag {
}
flag {
- name: "cross_user_role_platform_api_enabled"
- is_exported: true
- is_fixed_read_only: true
- namespace: "permissions"
- description: "Enable cross-user roles platform API"
- bug: "367732307"
-}
-
-flag {
name: "rate_limit_batched_note_op_async_callbacks_enabled"
is_fixed_read_only: true
is_exported: true
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c14854023b39..cf0e90fb43ce 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13710,6 +13710,14 @@ public final class Settings {
"render_shadows_in_compositor";
/**
+ * Policy to be used for the display shade when connected to an external display.
+ * @hide
+ */
+ @Readable
+ public static final String DEVELOPMENT_SHADE_DISPLAY_AWARENESS =
+ "shade_display_awareness";
+
+ /**
* Path to the WindowManager display settings file. If unset, the default file path will
* be used.
*
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index d42ec7c71830..0ce040d7f862 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -17,7 +17,6 @@ package android.service.autofill.augmented;
import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
import static android.service.autofill.augmented.AugmentedAutofillService.sVerbose;
-import static android.service.autofill.Flags.addAccessibilityTitleForAugmentedAutofillDropdown;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -37,7 +36,6 @@ import android.view.WindowManager;
import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.R;
import dalvik.system.CloseGuard;
@@ -210,12 +208,6 @@ public final class FillWindow implements AutoCloseable {
if (mWm != null && mFillView != null) {
try {
p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
- if (addAccessibilityTitleForAugmentedAutofillDropdown()) {
- p.accessibilityTitle =
- mFillView
- .getContext()
- .getString(R.string.autofill_picker_accessibility_title);
- }
if (!mShowing) {
mWm.addView(mFillView, p);
mShowing = true;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 609f1ef06612..1d27574eca8c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4435,7 +4435,8 @@ public final class ViewRootImpl implements ViewParent,
// merged with a sync group or BLASTBufferQueue before making it to this point
// But better a one or two frame flicker than steady-state broken from dropping
// whatever is in this transaction
- mPendingTransaction.apply();
+ // apply immediately with bbq apply token
+ mergeWithNextTransaction(mPendingTransaction, 0);
mHasPendingTransactions = false;
}
mSyncBuffer = false;
@@ -5501,7 +5502,8 @@ public final class ViewRootImpl implements ViewParent,
Log.d(mTag, "Pending transaction will not be applied in sync with a draw due to "
+ logReason);
}
- pendingTransaction.apply();
+ // apply immediately with bbq apply token
+ mergeWithNextTransaction(pendingTransaction, 0);
}
}
/**
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 6caa20e29c17..1707e61b28e4 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -175,13 +175,6 @@ flag {
}
flag {
- name: "enable_a11y_metrics"
- namespace: "lse_desktop_experience"
- description: "Whether to enable log collection for a11y actions in desktop windowing mode"
- bug: "341319597"
-}
-
-flag {
name: "enable_caption_compat_inset_force_consumption"
namespace: "lse_desktop_experience"
description: "Enables force-consumption of caption bar insets for immersive apps in freeform"
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index c160b42f8b6b..5c08dc6be1a0 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -3133,9 +3133,9 @@ public class ParsingPackageUtils {
private static ParseResult<ParsingPackage> parseAdoptPermissions(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
- TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestOriginalPackage);
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAdoptPermissions);
try {
- String name = nonConfigString(0, R.styleable.AndroidManifestOriginalPackage_name, sa);
+ String name = nonConfigString(0, R.styleable.AndroidManifestAdoptPermissions_name, sa);
if (name != null) {
pkg.addAdoptPermission(name);
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 169a9e8585b0..31d9770f6ac4 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -448,6 +448,17 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
mSenderView.setVisibility(hidden ? GONE : VISIBLE);
}
+ private void updateIconVisibility() {
+ if (Flags.notificationsRedesignTemplates() && !mIsInConversation) {
+ // We don't show any icon (other than the app icon) in the collapsed form. For
+ // conversations, keeping this container helps with aligning the message to the icon
+ // when collapsed, but the old messaging style already has this alignment built into
+ // the template like all other layouts. Conversations are special because we use the
+ // same base layout for both the collapsed and expanded views.
+ mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
+ }
+ }
+
@Override
public boolean hasDifferentHeightWhenFirst() {
return mCanHideSenderIfFirst && !mSingleLine && !TextUtils.isEmpty(mSenderName);
@@ -703,6 +714,7 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
updateMaxDisplayedLines();
updateClipRect();
updateSenderVisibility();
+ updateIconVisibility();
}
}
@@ -716,13 +728,16 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
* @param isInConversation is this in a conversation
*/
public void setIsInConversation(boolean isInConversation) {
- if (Flags.notificationsRedesignTemplates()) {
- // No alignment adjustments are necessary in the redesign, as the size of the icons
- // in both conversations and old messaging notifications are the same.
- return;
- }
if (mIsInConversation != isInConversation) {
mIsInConversation = isInConversation;
+
+ if (Flags.notificationsRedesignTemplates()) {
+ updateIconVisibility();
+ // No other alignment adjustments are necessary in the redesign, as the size of the
+ // icons in both conversations and old messaging notifications are the same.
+ return;
+ }
+
MarginLayoutParams layoutParams =
(MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
layoutParams.width = mIsInConversation
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 70dd10f2c371..5fa8125ced10 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -722,7 +722,7 @@ public interface AndroidPackage {
* The names of packages to adopt ownership of permissions from, parsed under {@link
* ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
*
- * @see R.styleable#AndroidManifestOriginalPackage_name
+ * @see R.styleable#AndroidManifestAdoptPermissions_name
* @hide
*/
@NonNull
diff --git a/core/jni/android_view_SurfaceControlActivePictureListener.cpp b/core/jni/android_view_SurfaceControlActivePictureListener.cpp
index 91849c1514cc..15132db2a569 100644
--- a/core/jni/android_view_SurfaceControlActivePictureListener.cpp
+++ b/core/jni/android_view_SurfaceControlActivePictureListener.cpp
@@ -106,12 +106,11 @@ struct SurfaceControlActivePictureListener : public gui::BnActivePictureListener
}
status_t startListening() {
- // TODO(b/337330263): Make SF multiple-listener capable
- return SurfaceComposerClient::setActivePictureListener(this);
+ return SurfaceComposerClient::addActivePictureListener(this);
}
status_t stopListening() {
- return SurfaceComposerClient::setActivePictureListener(nullptr);
+ return SurfaceComposerClient::removeActivePictureListener(this);
}
protected:
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 82cad8b3a477..4a948dd91fb0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8307,20 +8307,21 @@
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows a trusted application to perform actions on behalf of users inside of
+ <!-- Allows a trusted application to perform actions on behalf of users inside of
applications with privacy guarantees from the system.
<p>This permission is currently only granted to system packages in the
{@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
requirements outlined in the Android CDD section "9.8.6 Content Capture".
<p>Apps are not able to opt-out from caller having this permission.
<p>Protection level: internal|role
+ @SystemApi
@hide
- @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") -->
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="internal|role" />
- <!-- @SystemApi Allows an application to perform actions on behalf of users inside of
+ <!-- Allows an application to perform actions on behalf of users inside of
applications.
<p>This permission is currently only granted to preinstalled / system apps having the
{@link android.app.role.ASSISTANT} role.
@@ -8328,8 +8329,7 @@
limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
instead.
<p>Protection level: internal|role
- @hide
- @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") -->
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="internal|role" />
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index 63872aff8dd0..fc727e1c72f5 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -20,7 +20,6 @@
android:id="@+id/notification_header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_2025_header_height"
- android:layout_marginBottom="@dimen/notification_header_margin_bottom"
android:clipChildren="false"
android:gravity="center_vertical"
android:orientation="horizontal"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index a06d184fd147..8c6fd1dfc47e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2892,6 +2892,17 @@
<attr name="name" />
</declare-styleable>
+ <!-- Private tag to declare the package name that the permissions of this package
+ is based on. Only used for packages installed in the system image. If
+ given, the permissions from the other package will be propagated into the
+ new package.
+
+ <p>This appears as a child tag of the root
+ {@link #AndroidManifest manifest} tag. -->
+ <declare-styleable name="AndroidManifestAdoptPermissions" parent="AndroidManifest">
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>processes</code> tag specifies the processes the application will run code in
and optionally characteristics of those processes. This tag is optional; if not
specified, components will simply run in the processes they specify. If supplied,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 89184bcc3721..3d023c3e1d11 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2466,9 +2466,6 @@
<string name="config_systemCallStreaming" translatable="false"></string>
<!-- The name of the package that will hold the default retail demo role. -->
<string name="config_defaultRetailDemo" translatable="false"></string>
- <!-- The name of the package that will hold the default reserved for testing profile group
- exclusivity role. -->
- <string name="config_defaultReservedForTestingProfileGroupExclusivity" translatable="false">android.app.rolemultiuser.cts.app</string>
<!-- The component name of the wear service class that will be started by the system server. -->
<string name="config_wearServiceComponent" translatable="false"></string>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 20ae29659783..666f1cf39fe3 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -506,4 +506,10 @@
<!-- Whether to allow TN scanning during satellite session. -->
<bool name="config_satellite_allow_tn_scanning_during_satellite_session">true</bool>
<java-symbol type="bool" name="config_satellite_allow_tn_scanning_during_satellite_session" />
+
+ <!-- List of integer tag Ids representing VZW satellite coverage. -->
+ <integer-array name="config_verizon_satellite_enabled_tagids">
+ </integer-array>
+ <java-symbol type="array" name="config_verizon_satellite_enabled_tagids" />
+
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 8259ce3c0aee..e82992b91783 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -151,9 +151,8 @@
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
@hide @SystemApi -->
<public name="config_systemDependencyInstaller" />
- <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
- @hide @SystemApi -->
- <public name="config_defaultReservedForTestingProfileGroupExclusivity" />
+ <!-- @hide @SystemApi -->
+ <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" />
<!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
@hide @SystemApi -->
<public name="config_systemVendorIntelligence" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index bd273377984d..e9dfdd826572 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,7 +16,6 @@
package android.app;
-import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
@@ -711,7 +710,6 @@ public class PropertyInvalidatedCacheTests {
}
}
- @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
@Test
public void testCachingNulls() {
TestCache cache = new TestCache(new Args(MODULE_TEST)
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index bb8356f7aebe..fc04e6438ac6 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -16,7 +16,6 @@
package android.os;
-import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
import static org.junit.Assert.assertEquals;
@@ -511,7 +510,6 @@ public class IpcDataCacheTest {
IpcDataCache.setTestMode(true);
}
- @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
@Test
public void testCachingNulls() {
IpcDataCache.Config c =
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2c542ec31a20..2398e7134b34 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -679,4 +679,8 @@ applications that come with the platform
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.ENTER_TRADE_IN_MODE"/>
</privapp-permissions>
+
+ <privapp-permissions package="com.android.wm.shell">
+ <permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+ </privapp-permissions>
</permissions>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_header_maximize_menu_button_progress_indicator_layout.xml b/libs/WindowManager/Shell/res/layout/desktop_header_maximize_menu_button_progress_indicator_layout.xml
new file mode 100644
index 000000000000..5a39c83b1e92
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_header_maximize_menu_button_progress_indicator_layout.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="44dp"
+ android:layout_height="40dp"
+ android:importantForAccessibility="noHideDescendants">
+ <ProgressBar
+ android:id="@+id/progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:progressDrawable="@drawable/circular_progress"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:indeterminate="false"
+ android:layout_marginHorizontal="6dp"
+ android:layout_marginVertical="4dp"
+ android:visibility="invisible"/>
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index b734d2d81455..059e9e1a7895 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -17,21 +17,14 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <FrameLayout
+ <ViewStub
+ android:id="@+id/stub_progress_bar_container"
+ android:inflatedId="@+id/inflatedProgressBarContainer"
+ android:layout="@layout/desktop_header_maximize_menu_button_progress_indicator_layout"
android:layout_width="44dp"
android:layout_height="40dp"
- android:importantForAccessibility="noHideDescendants">
- <ProgressBar
- android:id="@+id/progress_bar"
- style="?android:attr/progressBarStyleHorizontal"
- android:progressDrawable="@drawable/circular_progress"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:indeterminate="false"
- android:layout_marginHorizontal="6dp"
- android:layout_marginVertical="4dp"
- android:visibility="invisible"/>
- </FrameLayout>
+ android:importantForAccessibility="noHideDescendants"
+ />
<ImageButton
android:id="@+id/maximize_window"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 68c42d6a2648..06a55d3dbbd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -24,7 +24,6 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
-import android.content.Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER
import android.content.pm.PackageManager
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState
@@ -60,13 +59,15 @@ fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean {
* Returns intent if there is a browser application available to handle the uri. Otherwise, returns
* null.
*/
-fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? {
+fun getBrowserIntent(uri: Uri, packageManager: PackageManager, userId: Int): Intent? {
val intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER)
.setData(uri)
.addFlags(FLAG_ACTIVITY_NEW_TASK)
- // If there is no browser application available to handle intent, return null
- val component = intent.resolveActivity(packageManager) ?: return null
- intent.setComponent(component)
+ // If there is a browser application available to handle the intent, return the intent.
+ // Otherwise, return null.
+ val resolveInfo = packageManager.resolveActivityAsUser(intent, /* flags= */ 0, userId)
+ ?: return null
+ intent.setComponent(resolveInfo.componentInfo.componentName)
return intent
}
@@ -74,14 +75,17 @@ fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? {
* Returns intent if there is a non-browser application available to handle the uri. Otherwise,
* returns null.
*/
-fun getAppIntent(uri: Uri, packageManager: PackageManager): Intent? {
- val intent = Intent(ACTION_VIEW, uri).apply {
- flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
+fun getAppIntent(uri: Uri, packageManager: PackageManager, userId: Int): Intent? {
+ val intent = Intent(ACTION_VIEW, uri).addFlags(FLAG_ACTIVITY_NEW_TASK)
+ val resolveInfo = packageManager.resolveActivityAsUser(intent, /* flags= */ 0, userId)
+ ?: return null
+ // If there is a non-browser application available to handle the intent, return the intent.
+ // Otherwise, return null.
+ if (resolveInfo.activityInfo != null && !resolveInfo.handleAllWebDataURI) {
+ intent.setComponent(resolveInfo.componentInfo.componentName)
+ return intent
}
- // If there is no application available to handle intent, return null
- val component = intent.resolveActivity(packageManager) ?: return null
- intent.setComponent(component)
- return intent
+ return null
}
/**
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 bec73a1500a7..9aba3aaa3268 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
@@ -2072,10 +2072,7 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
- if (mLayerView != null) {
- // TODO (b/273316505) handle suppression changes, although might not need to
- // to do anything on the layerview side for this...
- }
+ // Nothing to do for our views, handled by launcher / in the bubble bar.
}
@Override
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 10054a1727fc..94a6e5862b6c 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
@@ -115,6 +115,7 @@ public abstract class Pip2Module {
PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
+ PhonePipMenuController pipMenuController,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -123,7 +124,8 @@ public abstract class Pip2Module {
context, shellInit, shellCommandHandler, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipAppOpsListener, mainExecutor));
+ pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
+ mainExecutor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 50187d552b09..d404634b0db0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -239,7 +239,6 @@ class DesktopMixedTransitionHandler(
pending.minimizingTask?.let { minimizingTask -> findTaskChange(info, minimizingTask) }
val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask)
if (launchChange == null) {
- check(minimizeChange == null)
check(immersiveExitChange == null)
logV("No launch Change, returning")
return false
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 9b7c3a4d929f..609ac0aac381 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
@@ -592,10 +592,15 @@ class DesktopTasksController(
val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
- val requestInfo = TransitionRequestInfo(
- TRANSIT_PIP, /* triggerTask= */ null, taskInfo, /* remoteTransition= */ null,
- /* displayChange= */ null, /* flags= */ 0
- )
+ val requestInfo =
+ TransitionRequestInfo(
+ TRANSIT_PIP,
+ /* triggerTask= */ null,
+ taskInfo,
+ /* remoteTransition= */ null,
+ /* displayChange= */ null,
+ /* flags= */ 0,
+ )
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
pendingPipTransitionAndTask =
@@ -759,15 +764,12 @@ class DesktopTasksController(
displayId: Int = DEFAULT_DISPLAY,
): IBinder {
val taskIdToMinimize =
- if (launchingTaskId != null) {
- addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
- } else {
- logW("Starting desktop task launch without checking the task-limit")
- // TODO(b/378920066): This currently does not respect the desktop window limit.
- // It's possible that |launchingTaskId| is null when launching using an intent, and
- // the task-limit should be respected then too.
- null
- }
+ addAndGetMinimizeChanges(
+ displayId,
+ wct,
+ newTaskId = launchingTaskId,
+ launchingNewIntent = launchingTaskId == null,
+ )
val exitImmersiveResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -1409,7 +1411,7 @@ class DesktopTasksController(
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
- finishT: Transaction?
+ finishT: Transaction?,
) {
pendingPipTransitionAndTask?.let { (pipTransition, taskId) ->
if (transition == pipTransition) {
@@ -1985,10 +1987,14 @@ class DesktopTasksController(
private fun addAndGetMinimizeChanges(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskId: Int,
+ newTaskId: Int?,
+ launchingNewIntent: Boolean = false,
): Int? {
if (!desktopTasksLimiter.isPresent) return null
- return desktopTasksLimiter.get().addAndGetMinimizeTaskChanges(displayId, wct, newTaskId)
+ require(newTaskId == null || !launchingNewIntent)
+ return desktopTasksLimiter
+ .get()
+ .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
}
private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 635078e68a00..45faba6e341f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -214,12 +214,17 @@ class DesktopTasksLimiter(
fun addAndGetMinimizeTaskChanges(
displayId: Int,
wct: WindowContainerTransaction,
- newFrontTaskId: Int,
+ newFrontTaskId: Int?,
+ launchingNewIntent: Boolean = false,
): Int? {
logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId)
val taskRepository = desktopUserRepositories.current
val taskIdToMinimize =
- getTaskIdToMinimize(taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId)
+ getTaskIdToMinimize(
+ taskRepository.getExpandedTasksOrdered(displayId),
+ newFrontTaskId,
+ launchingNewIntent,
+ )
// If it's a running task, reorder it to back.
taskIdToMinimize
?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
@@ -242,15 +247,24 @@ class DesktopTasksLimiter(
* Returns the minimized task from the list of visible tasks ordered from front to back with the
* new task placed in front of other tasks.
*/
- fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>, newTaskIdInFront: Int? = null): Int? {
+ fun getTaskIdToMinimize(
+ visibleOrderedTasks: List<Int>,
+ newTaskIdInFront: Int? = null,
+ launchingNewIntent: Boolean = false,
+ ): Int? {
return getTaskIdToMinimize(
- createOrderedTaskListWithGivenTaskInFront(visibleOrderedTasks, newTaskIdInFront)
+ createOrderedTaskListWithGivenTaskInFront(visibleOrderedTasks, newTaskIdInFront),
+ launchingNewIntent,
)
}
/** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
- private fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>): Int? {
- if (visibleOrderedTasks.size <= maxTasksLimit) {
+ private fun getTaskIdToMinimize(
+ visibleOrderedTasks: List<Int>,
+ launchingNewIntent: Boolean,
+ ): Int? {
+ val newTasksOpening = if (launchingNewIntent) 1 else 0
+ if (visibleOrderedTasks.size + newTasksOpening <= maxTasksLimit) {
logV("No need to minimize; tasks below limit")
// No need to minimize anything
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 3cc477602cf0..9c3e815b389d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -781,6 +781,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// cancel any running animator, as it is using stale display layout information
animator.cancel();
}
+ mMenuController.hideMenu();
onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index a849b9de59ce..8c6d5f5c6660 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -97,6 +97,7 @@ public class PipController implements ConfigurationChangeListener,
private final PipTransitionState mPipTransitionState;
private final PipTouchHandler mPipTouchHandler;
private final PipAppOpsListener mPipAppOpsListener;
+ private final PhonePipMenuController mPipMenuController;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
@@ -141,6 +142,7 @@ public class PipController implements ConfigurationChangeListener,
PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
+ PhonePipMenuController pipMenuController,
ShellExecutor mainExecutor) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
@@ -157,6 +159,7 @@ public class PipController implements ConfigurationChangeListener,
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipTouchHandler = pipTouchHandler;
mPipAppOpsListener = pipAppOpsListener;
+ mPipMenuController = pipMenuController;
mMainExecutor = mainExecutor;
mImpl = new PipImpl();
@@ -183,6 +186,7 @@ public class PipController implements ConfigurationChangeListener,
PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
+ PhonePipMenuController pipMenuController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -192,7 +196,8 @@ public class PipController implements ConfigurationChangeListener,
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipAppOpsListener, mainExecutor);
+ pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
+ mainExecutor);
}
public PipImpl getPipImpl() {
@@ -329,6 +334,7 @@ public class PipController implements ConfigurationChangeListener,
}
mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+ mPipMenuController.hideMenu();
if (mPipTransitionState.isInFixedRotation()) {
// Do not change the bounds when in fixed rotation, but do update the movement bounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e48c887c625f..d0c21c9ec7c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -20,6 +20,7 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_A
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
@@ -145,6 +146,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
@@ -2766,6 +2768,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final @WindowManager.TransitionType int type = request.getType();
final boolean isOpening = isOpeningType(type);
final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ final boolean inDesktopMode = DesktopModeStatus.canEnterDesktopMode(mContext)
+ && triggerTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final StageTaskListener stage = getStageOfTask(triggerTask);
if (isOpening && inFullscreen) {
@@ -2820,6 +2824,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.setDismissTransition(transition, stageType,
EXIT_REASON_FULLSCREEN_REQUEST);
}
+ } else if (isOpening && inDesktopMode) {
+ // If the app being opened is in Desktop mode, set it to full screen and dismiss
+ // split screen stage.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
+ out.setWindowingMode(triggerTask.token, WINDOWING_MODE_UNDEFINED)
+ .setBounds(triggerTask.token, null);
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 0b919668f7fe..792f5cad3418 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -468,7 +468,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
- 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
+ 0 /* ctrlType */, e.getDisplayId(), e.getRawX(0), e.getRawY(0));
mIsDragging = false;
return false;
}
@@ -481,6 +481,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
+ e.getDisplayId(),
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true;
return true;
@@ -492,6 +493,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
+ e.getDisplayId(),
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 5a05861c3a88..7928e5ed4188 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1140,7 +1140,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (dragAllowed) {
mDragPointerId = e.getPointerId(0);
final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
- 0 /* ctrlType */, e.getRawX(0),
+ 0 /* ctrlType */, e.getDisplayId(), e.getRawX(0),
e.getRawY(0));
updateDragStatus(e.getActionMasked());
mOnDragStartInitialBounds.set(initialBounds);
@@ -1161,6 +1161,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
+ e.getDisplayId(),
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
@@ -1191,6 +1192,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
(int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)),
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
+ e.getDisplayId(),
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
// Tasks bounds haven't actually been updated (only its leash), so pass to
// DesktopTasksController to allow secondary transformations (i.e. snap resizing
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index febf5669d12d..0d1960ad6e29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -617,14 +617,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
if (browserLink == null) return null;
- return AppToWebUtils.getBrowserIntent(browserLink, mContext.getPackageManager());
+ return AppToWebUtils.getBrowserIntent(browserLink, mContext.getPackageManager(),
+ mUserContext.getUserId());
}
@Nullable
private Intent getAppLink() {
return mWebUri == null ? null
- : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager());
+ : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager(),
+ mUserContext.getUserId());
}
private boolean isBrowserApp() {
@@ -779,12 +781,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final Point position = new Point(mResult.mCaptionX, 0);
if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
== SPLIT_POSITION_BOTTOM_OR_RIGHT
- && mDisplayController.getDisplayLayout(mTaskInfo.displayId).isLandscape()
) {
- // If this is the right split task, add left stage's width.
- final Rect leftStageBounds = new Rect();
- mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
- position.x += leftStageBounds.width();
+ if (mSplitScreenController.isLeftRightSplit()) {
+ // If this is the right split task, add left stage's width.
+ final Rect leftStageBounds = new Rect();
+ mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
+ position.x += leftStageBounds.width();
+ } else {
+ final Rect bottomStageBounds = new Rect();
+ mSplitScreenController.getRefStageBounds(new Rect(), bottomStageBounds);
+ position.y += bottomStageBounds.top;
+ }
}
return position;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 421ffd929fb2..3eebdb048f0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -41,25 +41,30 @@ public interface DragPositioningCallback {
*
* @param ctrlType {@link CtrlType} indicating the direction of resizing, use
* {@code 0} to indicate it's a move
+ * @param displayId the ID of the display where the drag starts
* @param x x coordinate in window decoration coordinate system where the drag starts
* @param y y coordinate in window decoration coordinate system where the drag starts
* @return the starting task bounds
*/
- Rect onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
+ Rect onDragPositioningStart(@CtrlType int ctrlType, int displayId, float x, float y);
/**
* Called when the pointer moves during a drag-resize or drag-move.
+ *
+ * @param displayId the ID of the display where the pointer is currently located
* @param x x coordinate in window decoration coordinate system of the new pointer location
* @param y y coordinate in window decoration coordinate system of the new pointer location
* @return the updated task bounds
*/
- Rect onDragPositioningMove(float x, float y);
+ Rect onDragPositioningMove(int displayId, float x, float y);
/**
* Called when a drag-resize or drag-move stops.
+ *
+ * @param displayId the ID of the display where the pointer is located when drag stops
* @param x x coordinate in window decoration coordinate system where the drag resize stops
* @param y y coordinate in window decoration coordinate system where the drag resize stops
* @return the final bounds for the dragged task
*/
- Rect onDragPositioningEnd(float x, float y);
+ Rect onDragPositioningEnd(int displayId, float x, float y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index a6d503d0d991..7d1471f44674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -454,7 +454,7 @@ class DragResizeInputListener implements AutoCloseable {
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
- rawX, rawY);
+ e.getDisplayId(), rawX, rawY);
mLastMotionEventOnDown = e;
mResizeTrigger = (ctrlType == CTRL_TYPE_BOTTOM || ctrlType == CTRL_TYPE_TOP
|| ctrlType == CTRL_TYPE_RIGHT || ctrlType == CTRL_TYPE_LEFT)
@@ -489,7 +489,8 @@ class DragResizeInputListener implements AutoCloseable {
}
final float rawX = e.getRawX(dragPointerIndex);
final float rawY = e.getRawY(dragPointerIndex);
- final Rect taskBounds = mCallback.onDragPositioningMove(rawX, rawY);
+ final Rect taskBounds = mCallback.onDragPositioningMove(e.getDisplayId(),
+ rawX, rawY);
updateInputSinkRegionForDrag(taskBounds);
result = true;
break;
@@ -505,7 +506,7 @@ class DragResizeInputListener implements AutoCloseable {
TAG, e.getActionMasked());
break;
}
- final Rect taskBounds = mCallback.onDragPositioningEnd(
+ final Rect taskBounds = mCallback.onDragPositioningEnd(e.getDisplayId(),
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
// If taskBounds has changed, setGeometry will be called and update the
// sink region. Otherwise, we should revert it here.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index c8aff78cbb36..5b027f3c039e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -168,7 +168,10 @@ public final class DragResizeWindowGeometry {
return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
}
- static boolean isEdgeResizePermitted(@NonNull MotionEvent e) {
+ /**
+ * Whether resizing a window from the edge is permitted based on the motion event.
+ */
+ public static boolean isEdgeResizePermitted(@NonNull MotionEvent e) {
if (ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue()) {
return e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
|| e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
index 3885761d0742..ab30d617af54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt
@@ -47,10 +47,11 @@ class FixedAspectRatioTaskPositionerDecorator (
private var startingAspectRatio = 0f
private var isTaskPortrait = false
- override fun onDragPositioningStart(@CtrlType ctrlType: Int, x: Float, y: Float): Rect {
+ override fun onDragPositioningStart(
+ @CtrlType ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
originalCtrlType = ctrlType
if (!requiresFixedAspectRatio()) {
- return super.onDragPositioningStart(originalCtrlType, x, y)
+ return super.onDragPositioningStart(originalCtrlType, displayId, x, y)
}
lastRepositionedBounds.set(getBounds(windowDecoration.mTaskInfo))
@@ -72,27 +73,27 @@ class FixedAspectRatioTaskPositionerDecorator (
val verticalMidPoint = lastRepositionedBounds.top + (startingBoundHeight / 2)
edgeResizeCtrlType = originalCtrlType +
if (y < verticalMidPoint) CTRL_TYPE_TOP else CTRL_TYPE_BOTTOM
- super.onDragPositioningStart(edgeResizeCtrlType, x, y)
+ super.onDragPositioningStart(edgeResizeCtrlType, displayId, x, y)
}
CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM -> {
val horizontalMidPoint = lastRepositionedBounds.left + (startingBoundWidth / 2)
edgeResizeCtrlType = originalCtrlType +
if (x < horizontalMidPoint) CTRL_TYPE_LEFT else CTRL_TYPE_RIGHT
- super.onDragPositioningStart(edgeResizeCtrlType, x, y)
+ super.onDragPositioningStart(edgeResizeCtrlType, displayId, x, y)
}
// If resize is corner resize, no alteration to the ctrlType needs to be made.
else -> {
edgeResizeCtrlType = CTRL_TYPE_UNDEFINED
- super.onDragPositioningStart(originalCtrlType, x, y)
+ super.onDragPositioningStart(originalCtrlType, displayId, x, y)
}
}
)
return lastRepositionedBounds
}
- override fun onDragPositioningMove(x: Float, y: Float): Rect {
+ override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
if (!requiresFixedAspectRatio()) {
- return super.onDragPositioningMove(x, y)
+ return super.onDragPositioningMove(displayId, x, y)
}
val diffX = x - lastValidPoint.x
@@ -103,7 +104,7 @@ class FixedAspectRatioTaskPositionerDecorator (
// Drag coordinate falls within valid region (90 - 180 degrees or 270- 360
// degrees from the corner the previous valid point). Allow resize with adjusted
// coordinates to maintain aspect ratio.
- lastRepositionedBounds.set(dragAdjustedMove(x, y))
+ lastRepositionedBounds.set(dragAdjustedMove(displayId, x, y))
}
}
CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, CTRL_TYPE_TOP + CTRL_TYPE_RIGHT -> {
@@ -111,28 +112,28 @@ class FixedAspectRatioTaskPositionerDecorator (
// Drag coordinate falls within valid region (180 - 270 degrees or 0 - 90
// degrees from the corner the previous valid point). Allow resize with adjusted
// coordinates to maintain aspect ratio.
- lastRepositionedBounds.set(dragAdjustedMove(x, y))
+ lastRepositionedBounds.set(dragAdjustedMove(displayId, x, y))
}
}
CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT -> {
// If resize is on left or right edge, always adjust the y coordinate.
val adjustedY = getScaledChangeForY(x)
lastValidPoint.set(x, adjustedY)
- lastRepositionedBounds.set(super.onDragPositioningMove(x, adjustedY))
+ lastRepositionedBounds.set(super.onDragPositioningMove(displayId, x, adjustedY))
}
CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM -> {
// If resize is on top or bottom edge, always adjust the x coordinate.
val adjustedX = getScaledChangeForX(y)
lastValidPoint.set(adjustedX, y)
- lastRepositionedBounds.set(super.onDragPositioningMove(adjustedX, y))
+ lastRepositionedBounds.set(super.onDragPositioningMove(displayId, adjustedX, y))
}
}
return lastRepositionedBounds
}
- override fun onDragPositioningEnd(x: Float, y: Float): Rect {
+ override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
if (!requiresFixedAspectRatio()) {
- return super.onDragPositioningEnd(x, y)
+ return super.onDragPositioningEnd(displayId, x, y)
}
val diffX = x - lastValidPoint.x
@@ -144,55 +145,55 @@ class FixedAspectRatioTaskPositionerDecorator (
// Drag coordinate falls within valid region (90 - 180 degrees or 270- 360
// degrees from the corner the previous valid point). End resize with adjusted
// coordinates to maintain aspect ratio.
- return dragAdjustedEnd(x, y)
+ return dragAdjustedEnd(displayId, x, y)
}
// If end of resize is not within valid region, end resize from last valid
// coordinates.
- return super.onDragPositioningEnd(lastValidPoint.x, lastValidPoint.y)
+ return super.onDragPositioningEnd(displayId, lastValidPoint.x, lastValidPoint.y)
}
CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, CTRL_TYPE_TOP + CTRL_TYPE_RIGHT -> {
if ((diffX > 0 && diffY < 0) || (diffX < 0 && diffY > 0)) {
// Drag coordinate falls within valid region (180 - 260 degrees or 0 - 90
// degrees from the corner the previous valid point). End resize with adjusted
// coordinates to maintain aspect ratio.
- return dragAdjustedEnd(x, y)
+ return dragAdjustedEnd(displayId, x, y)
}
// If end of resize is not within valid region, end resize from last valid
// coordinates.
- return super.onDragPositioningEnd(lastValidPoint.x, lastValidPoint.y)
+ return super.onDragPositioningEnd(displayId, lastValidPoint.x, lastValidPoint.y)
}
CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT -> {
// If resize is on left or right edge, always adjust the y coordinate.
- return super.onDragPositioningEnd(x, getScaledChangeForY(x))
+ return super.onDragPositioningEnd(displayId, x, getScaledChangeForY(x))
}
CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM -> {
// If resize is on top or bottom edge, always adjust the x coordinate.
- return super.onDragPositioningEnd(getScaledChangeForX(y), y)
+ return super.onDragPositioningEnd(displayId, getScaledChangeForX(y), y)
}
else -> {
- return super.onDragPositioningEnd(x, y)
+ return super.onDragPositioningEnd(displayId, x, y)
}
}
}
- private fun dragAdjustedMove(x: Float, y: Float): Rect {
+ private fun dragAdjustedMove(displayId: Int, x: Float, y: Float): Rect {
val absDiffX = abs(x - lastValidPoint.x)
val absDiffY = abs(y - lastValidPoint.y)
if (absDiffY < absDiffX) {
lastValidPoint.set(getScaledChangeForX(y), y)
- return super.onDragPositioningMove(getScaledChangeForX(y), y)
+ return super.onDragPositioningMove(displayId, getScaledChangeForX(y), y)
}
lastValidPoint.set(x, getScaledChangeForY(x))
- return super.onDragPositioningMove(x, getScaledChangeForY(x))
+ return super.onDragPositioningMove(displayId, x, getScaledChangeForY(x))
}
- private fun dragAdjustedEnd(x: Float, y: Float): Rect {
+ private fun dragAdjustedEnd(displayId: Int, x: Float, y: Float): Rect {
val absDiffX = abs(x - lastValidPoint.x)
val absDiffY = abs(y - lastValidPoint.y)
if (absDiffY < absDiffX) {
- return super.onDragPositioningEnd(getScaledChangeForX(y), y)
+ return super.onDragPositioningEnd(displayId, getScaledChangeForX(y), y)
}
- return super.onDragPositioningEnd(x, getScaledChangeForY(x))
+ return super.onDragPositioningEnd(displayId, x, getScaledChangeForY(x))
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3efae9d6375a..2d6f7459e0ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -91,7 +91,7 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio
}
@Override
- public Rect onDragPositioningStart(int ctrlType, float x, float y) {
+ public Rect onDragPositioningStart(int ctrlType, int displayId, float x, float y) {
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -117,7 +117,7 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio
}
@Override
- public Rect onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(int displayId, float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
@@ -147,7 +147,7 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio
}
@Override
- public Rect onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(int displayId, float x, float y) {
// If task has been resized or task was dragged into area outside of
// mDisallowedAreaForEndBounds, apply WCT to finish it.
if (isResizing() && mHasDragResized) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 159759ede368..bb19a2cc2ad4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -694,7 +694,7 @@ class HandleMenu(
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
-
+ openByDefaultBtn.isGone = isBrowserApp
openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 376cd2a78baf..e23ebe6634ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -26,6 +26,7 @@ import android.graphics.drawable.RippleDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewStub
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ProgressBar
@@ -46,13 +47,17 @@ class MaximizeButtonView(
private val hoverProgressAnimatorSet = AnimatorSet()
var hoverDisabled = false
- private val progressBar: ProgressBar
+ private lateinit var stubProgressBarContainer: ViewStub
private val maximizeWindow: ImageButton
+ private val progressBar: ProgressBar by lazy {
+ (stubProgressBarContainer.inflate() as FrameLayout)
+ .requireViewById(R.id.progress_bar)
+ }
init {
LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true)
- progressBar = requireViewById(R.id.progress_bar)
+ stubProgressBarContainer = requireViewById(R.id.stub_progress_bar_container)
maximizeWindow = requireViewById(R.id.maximize_window)
}
@@ -115,21 +120,34 @@ class MaximizeButtonView(
requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" }
maximizeWindow.imageTintList = iconForegroundColor
maximizeWindow.background = rippleDrawable
- progressBar.progressTintList = ColorStateList.valueOf(baseForegroundColor)
- .withAlpha(OPACITY_15)
- progressBar.progressBackgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ stubProgressBarContainer.setOnInflateListener { _, inflated ->
+ val progressBar = (inflated as FrameLayout)
+ .requireViewById(R.id.progress_bar) as ProgressBar
+ progressBar.progressTintList = ColorStateList.valueOf(baseForegroundColor)
+ .withAlpha(OPACITY_15)
+ progressBar.progressBackgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ }
} else {
- if (darkMode) {
- progressBar.progressTintList = ColorStateList.valueOf(
+ val progressTint = if (darkMode) {
+ ColorStateList.valueOf(
resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark))
- maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
- R.color.desktop_mode_caption_button_color_selector_dark))
} else {
- progressBar.progressTintList = ColorStateList.valueOf(
+ ColorStateList.valueOf(
resources.getColor(R.color.desktop_mode_maximize_menu_progress_light))
- maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
- R.color.desktop_mode_caption_button_color_selector_light))
}
+ val backgroundTint = if (darkMode) {
+ ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_dark)
+ } else {
+ ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_light)
+ }
+ stubProgressBarContainer.setOnInflateListener { _, inflated ->
+ val progressBar = (inflated as FrameLayout)
+ .requireViewById(R.id.progress_bar) as ProgressBar
+ progressBar.progressTintList = progressTint
+ }
+ maximizeWindow.background?.setTintList(backgroundTint)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 1f03d7568130..e011cc08903b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -104,7 +104,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
@Override
- public Rect onDragPositioningStart(int ctrlType, float x, float y) {
+ public Rect onDragPositioningStart(int ctrlType, int displayId, float x, float y) {
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -136,7 +136,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
@Override
- public Rect onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(int displayId, float x, float y) {
if (Looper.myLooper() != mHandler.getLooper()) {
// This method must run on the shell main thread to use the correct Choreographer
// instance below.
@@ -170,7 +170,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
@Override
- public Rect onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(int displayId, float x, float y) {
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
mRepositionStartPoint);
if (isResizing()) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 39178cb2cd25..1e4d108a9cda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -442,6 +442,18 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() {
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
+ visibleOrderedTasks = tasks.map { it.taskId },
+ newTaskIdInFront = null,
+ launchingNewIntent = true)
+
+ // first == front, last == back
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
+ }
+
+ @Test
fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() {
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 0214da4660ad..aead0a7afb53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -1015,11 +1015,11 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onCaptionButtonTouchListener = onTouchListenerCaptor
)
- whenever(mockTaskPositioner.onDragPositioningStart(any(), any(), any()))
+ whenever(mockTaskPositioner.onDragPositioningStart(any(), any(), any(), any()))
.thenReturn(INITIAL_BOUNDS)
- whenever(mockTaskPositioner.onDragPositioningMove(any(), any()))
+ whenever(mockTaskPositioner.onDragPositioningMove(any(), any(), any()))
.thenReturn(INITIAL_BOUNDS)
- whenever(mockTaskPositioner.onDragPositioningEnd(any(), any()))
+ whenever(mockTaskPositioner.onDragPositioningEnd(any(), any(), any()))
.thenReturn(INITIAL_BOUNDS)
val view = mock(View::class.java)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 8a1a9b5ef80b..855b3ddd99b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -278,9 +278,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
final ActivityInfo activityInfo = createActivityInfo();
when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo);
- final ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.activityInfo = activityInfo;
- when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
+ final ResolveInfo resolveInfo = createResolveInfo(false /* handleAllWebDataUri */);
+ when(mMockPackageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(resolveInfo);
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
@@ -1664,11 +1664,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void browserApp_transferSessionUriUsedForBrowserAppWhenAvailable() {
// Make {@link AppToWebUtils#isBrowserApp} return true
- ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.handleAllWebDataURI = true;
- resolveInfo.activityInfo = createActivityInfo();
+ ResolveInfo browserResolveInfo = createResolveInfo(true /* handleAllWebUriData */);
when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
- .thenReturn(List.of(resolveInfo));
+ .thenReturn(List.of(browserResolveInfo));
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
@@ -1793,6 +1791,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
return windowDecor;
}
+ private ResolveInfo createResolveInfo(boolean handleAllWebDataURI) {
+ final ResolveInfo info = new ResolveInfo();
+ info.handleAllWebDataURI = handleAllWebDataURI;
+ info.activityInfo = createActivityInfo();
+ return info;
+ }
+
private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
new ActivityManager.TaskDescription.Builder();
@@ -1821,6 +1826,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
applicationInfo.packageName = "DesktopModeWindowDecorationTestPackage";
final ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = applicationInfo;
+ activityInfo.packageName = "DesktopModeWindowDecorationTestPackage";
activityInfo.name = "DesktopModeWindowDecorationTest";
return activityInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt
index ce17c1df50bc..3c3d6b6bb258 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt
@@ -68,9 +68,9 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
configuration.windowConfiguration.setBounds(PORTRAIT_BOUNDS)
}
doReturn(PORTRAIT_BOUNDS).`when`(mockTaskPositioner).onDragPositioningStart(
- any(), any(), any())
- doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningMove(any(), any())
- doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningEnd(any(), any())
+ any(), any(), any(), any())
+ doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningMove(any(), any(), any())
+ doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningEnd(any(), any(), any())
decoratedTaskPositioner = spy(
FixedAspectRatioTaskPositionerDecorator(
mockDesktopWindowDecoration, mockTaskPositioner)
@@ -87,7 +87,8 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
isResizeable = testCase.isResizeable
}
- decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY)
+ decoratedTaskPositioner.onDragPositioningStart(
+ testCase.ctrlType, DISPLAY_ID, originalX, originalY)
val capturedValues = getLatestOnStartArguments()
assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType)
@@ -102,7 +103,8 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val originalX = 0f
val originalY = 0f
- decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY)
+ decoratedTaskPositioner.onDragPositioningStart(
+ testCase.ctrlType, DISPLAY_ID, originalX, originalY)
val capturedValues = getLatestOnStartArguments()
assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType)
@@ -119,7 +121,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
val adjustedCtrlType = testCase.ctrlType + testCase.additionalEdgeCtrlType
val capturedValues = getLatestOnStartArguments()
@@ -134,13 +136,14 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
) {
val originalX = 0f
val originalY = 0f
- decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX)
+ decoratedTaskPositioner.onDragPositioningStart(
+ testCase.ctrlType, DISPLAY_ID, originalX, originalX)
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
isResizeable = testCase.isResizeable
}
decoratedTaskPositioner.onDragPositioningMove(
- originalX + SMALL_DELTA, originalY + SMALL_DELTA)
+ DISPLAY_ID, originalX + SMALL_DELTA, originalY + SMALL_DELTA)
val capturedValues = getLatestOnMoveArguments()
assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA)
@@ -156,13 +159,14 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
val updatedBounds = decoratedTaskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
startingPoint.x + testCase.dragDelta.x,
startingPoint.y + testCase.dragDelta.y)
- verify(mockTaskPositioner, never()).onDragPositioningMove(any(), any())
+ verify(mockTaskPositioner, never()).onDragPositioningMove(any(), any(), any())
assertThat(updatedBounds).isEqualTo(startingBounds)
}
@@ -176,10 +180,12 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
decoratedTaskPositioner.onDragPositioningMove(
- startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y)
+ DISPLAY_ID,
+ startingPoint.x + testCase.dragDelta.x,
+ startingPoint.y + testCase.dragDelta.y)
val adjustedDragDelta = calculateAdjustedDelta(
testCase.ctrlType, testCase.dragDelta, orientation)
@@ -202,9 +208,10 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
decoratedTaskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
startingPoint.x + testCase.dragDelta.x,
startingPoint.y + testCase.dragDelta.y)
@@ -227,13 +234,14 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
) {
val originalX = 0f
val originalY = 0f
- decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX)
+ decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, DISPLAY_ID,
+ originalX, originalX)
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
isResizeable = testCase.isResizeable
}
decoratedTaskPositioner.onDragPositioningEnd(
- originalX + SMALL_DELTA, originalY + SMALL_DELTA)
+ DISPLAY_ID, originalX + SMALL_DELTA, originalY + SMALL_DELTA)
val capturedValues = getLatestOnEndArguments()
assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA)
@@ -249,9 +257,10 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
decoratedTaskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
startingPoint.x + testCase.dragDelta.x,
startingPoint.y + testCase.dragDelta.y)
@@ -269,10 +278,12 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
decoratedTaskPositioner.onDragPositioningEnd(
- startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y)
+ DISPLAY_ID,
+ startingPoint.x + testCase.dragDelta.x,
+ startingPoint.y + testCase.dragDelta.y)
val adjustedDragDelta = calculateAdjustedDelta(
testCase.ctrlType, testCase.dragDelta, orientation)
@@ -295,9 +306,10 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds)
decoratedTaskPositioner.onDragPositioningStart(
- testCase.ctrlType, startingPoint.x, startingPoint.y)
+ testCase.ctrlType, DISPLAY_ID, startingPoint.x, startingPoint.y)
decoratedTaskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
startingPoint.x + testCase.dragDelta.x,
startingPoint.y + testCase.dragDelta.y)
@@ -322,7 +334,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
val captorCtrlType = argumentCaptor<Int>()
val captorCoordinates = argumentCaptor<Float>()
verify(mockTaskPositioner).onDragPositioningStart(
- captorCtrlType.capture(), captorCoordinates.capture(), captorCoordinates.capture())
+ captorCtrlType.capture(), any(), captorCoordinates.capture(), captorCoordinates.capture())
return CtrlCoordinateCapture(captorCtrlType.firstValue, captorCoordinates.firstValue,
captorCoordinates.secondValue)
@@ -335,7 +347,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
private fun getLatestOnMoveArguments(): PointF {
val captorCoordinates = argumentCaptor<Float>()
verify(mockTaskPositioner).onDragPositioningMove(
- captorCoordinates.capture(), captorCoordinates.capture())
+ any(), captorCoordinates.capture(), captorCoordinates.capture())
return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue)
}
@@ -347,7 +359,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
private fun getLatestOnEndArguments(): PointF {
val captorCoordinates = argumentCaptor<Float>()
verify(mockTaskPositioner).onDragPositioningEnd(
- captorCoordinates.capture(), captorCoordinates.capture())
+ any(), captorCoordinates.capture(), captorCoordinates.capture())
return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue)
}
@@ -358,7 +370,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
private fun getAndMockBounds(orientation: Orientation): Rect {
val mockBounds = if (orientation.isPortrait) PORTRAIT_BOUNDS else LANDSCAPE_BOUNDS
doReturn(mockBounds).`when`(mockTaskPositioner).onDragPositioningStart(
- any(), any(), any())
+ any(), any(), any(), any())
doReturn(mockBounds).`when`(decoratedTaskPositioner).getBounds(any())
return mockBounds
}
@@ -458,6 +470,7 @@ class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){
private val STARTING_ASPECT_RATIO = PORTRAIT_BOUNDS.height() / PORTRAIT_BOUNDS.width()
private const val LARGE_DELTA = 50f
private const val SMALL_DELTA = 30f
+ private const val DISPLAY_ID = 1
enum class Orientation(
val isPortrait: Boolean
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 3b80cb4936b9..cec52518edd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -150,11 +150,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_notMove_skipsTransitionOnEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -171,11 +173,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_noEffectiveMove_skipsTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -188,6 +192,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
})
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -204,11 +209,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_hasEffectiveMove_issuesTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat()
)
@@ -224,6 +231,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -242,6 +250,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_move_skipsDragResizingFlag() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // Move
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -250,11 +259,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.left.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -276,6 +286,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setsDragResizingFlag() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -284,11 +295,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -310,6 +322,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -318,11 +331,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 5
val newY = STARTING_BOUNDS.top.toFloat() + 95
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -340,6 +354,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -348,11 +363,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 95
val newY = STARTING_BOUNDS.top.toFloat() + 5
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -370,6 +386,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -378,11 +395,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 5
val newY = STARTING_BOUNDS.top.toFloat() + 105
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -400,6 +418,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -408,11 +427,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 105
val newY = STARTING_BOUNDS.top.toFloat() + 5
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -430,6 +450,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -438,11 +459,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 80
val newY = STARTING_BOUNDS.top.toFloat() + 80
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -456,6 +478,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -464,11 +487,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 95
val newY = STARTING_BOUNDS.top.toFloat() + 95
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -484,6 +508,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -492,11 +517,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 97
val newY = STARTING_BOUNDS.top.toFloat() + 97
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -510,6 +536,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_useMinWidthWhenValid() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -518,11 +545,12 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.right.toFloat() - 93
val newY = STARTING_BOUNDS.top.toFloat() + 93
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -535,6 +563,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_toDisallowedBounds_freezesAtLimit() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.bottom.toFloat()
)
@@ -546,6 +575,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.right + 10,
STARTING_BOUNDS.bottom + 10)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newBounds.right.toFloat(),
newBounds.bottom.toFloat()
)
@@ -559,11 +589,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISALLOWED_RESIZE_AREA.top
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newBounds2.right.toFloat(),
newBounds2.bottom.toFloat()
)
- taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newBounds2.right.toFloat(),
+ newBounds2.bottom.toFloat())
// The first resize falls in the allowed area, verify there's a change for it.
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
@@ -629,6 +661,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
mockWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -645,6 +678,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
mockWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -661,6 +695,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
mockWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -729,11 +764,13 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() - 20,
STARTING_BOUNDS.top.toFloat() - 20
)
@@ -742,6 +779,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
assertTrue(taskPositioner.isResizingOrAnimating)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -785,15 +823,18 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
) {
taskPositioner.onDragPositioningStart(
ctrlType,
+ DISPLAY_ID,
startX,
startY
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
endX,
endY
)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
endX,
endY
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index e7df8643ba66..eb8c0dd365a3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -168,12 +168,14 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -191,11 +193,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 60,
STARTING_BOUNDS.top.toFloat() + 100
)
@@ -208,6 +212,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
eq(rectAfterMove.top.toFloat()))
val endBounds = taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 70,
STARTING_BOUNDS.top.toFloat() + 20
)
@@ -226,11 +231,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -248,6 +255,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
})
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.right.toFloat() + 20,
STARTING_BOUNDS.top.toFloat() + 20
)
@@ -266,17 +274,20 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
@Test
fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
+ DISPLAY_ID,
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -300,6 +311,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -307,11 +319,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
val newX = STARTING_BOUNDS.left.toFloat() + 5
val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
newX,
newY
)
- taskPositioner.onDragPositioningEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -326,6 +339,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -342,6 +356,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -358,6 +373,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -422,11 +438,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat() - 20,
STARTING_BOUNDS.top.toFloat() - 20
)
@@ -436,6 +454,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
@@ -501,15 +520,18 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
) {
taskPositioner.onDragPositioningStart(
ctrlType,
+ DISPLAY_ID,
startX,
startY
)
taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
endX,
endY
)
taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
endX,
endY
)
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 50387548b4ab..bcb70019b3ac 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -53,7 +53,7 @@ import java.util.stream.Collectors;
* The format of the media data is specified as key/value pairs. Keys are strings. Values can
* be integer, long, float, String or ByteBuffer.
* <p>
- * The feature metadata is specificed as string/boolean pairs.
+ * The feature metadata is specified as string/boolean pairs.
* <p>
* Keys common to all audio/video formats, <b>all keys not marked optional are mandatory</b>:
*
@@ -1244,12 +1244,12 @@ public final class MediaFormat {
/**
* An optional key describing the desired encoder latency in frames. This is an optional
- * parameter that applies only to video encoders. If encoder supports it, it should ouput
+ * parameter that applies only to video encoders. If encoder supports it, it should output
* at least one output frame after being queued the specified number of frames. This key
* is ignored if the video encoder does not support the latency feature. Use the output
* format to verify that this feature was enabled and the actual value used by the encoder.
* <p>
- * If the key is not specified, the default latency will be implenmentation specific.
+ * If the key is not specified, the default latency will be implementation specific.
* The associated value is an integer.
*/
public static final String KEY_LATENCY = "latency";
@@ -1507,16 +1507,16 @@ public final class MediaFormat {
*/
public static final String KEY_COLOR_STANDARD = "color-standard";
- /** BT.709 color chromacity coordinates with KR = 0.2126, KB = 0.0722. */
+ /** BT.709 color chromaticity coordinates with KR = 0.2126, KB = 0.0722. */
public static final int COLOR_STANDARD_BT709 = 1;
- /** BT.601 625 color chromacity coordinates with KR = 0.299, KB = 0.114. */
+ /** BT.601 625 color chromaticity coordinates with KR = 0.299, KB = 0.114. */
public static final int COLOR_STANDARD_BT601_PAL = 2;
- /** BT.601 525 color chromacity coordinates with KR = 0.299, KB = 0.114. */
+ /** BT.601 525 color chromaticity coordinates with KR = 0.299, KB = 0.114. */
public static final int COLOR_STANDARD_BT601_NTSC = 4;
- /** BT.2020 color chromacity coordinates with KR = 0.2627, KB = 0.0593. */
+ /** BT.2020 color chromaticity coordinates with KR = 0.2627, KB = 0.0593. */
public static final int COLOR_STANDARD_BT2020 = 6;
/** @hide */
@@ -2150,7 +2150,7 @@ public final class MediaFormat {
* Sets the value of a string key.
* <p>
* If value is {@code null}, it sets a null value that behaves similarly to a missing key.
- * This could be used prior to API level {@link android os.Build.VERSION_CODES#Q} to effectively
+ * This could be used prior to API level {@link android.os.Build.VERSION_CODES#Q} to effectively
* remove a key.
*/
public final void setString(@NonNull String name, @Nullable String value) {
@@ -2161,7 +2161,7 @@ public final class MediaFormat {
* Sets the value of a ByteBuffer key.
* <p>
* If value is {@code null}, it sets a null value that behaves similarly to a missing key.
- * This could be used prior to API level {@link android os.Build.VERSION_CODES#Q} to effectively
+ * This could be used prior to API level {@link android.os.Build.VERSION_CODES#Q} to effectively
* remove a key.
*/
public final void setByteBuffer(@NonNull String name, @Nullable ByteBuffer bytes) {
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 248efde4902c..bbb03e77c8c9 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -934,6 +934,15 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Returns whether this route supports {@link #FLAG_ROUTING_TYPE_REMOTE remote routing}.
+ *
+ * @hide
+ */
+ public boolean supportsRemoteRouting() {
+ return (mRoutingTypeFlags & MediaRoute2Info.FLAG_ROUTING_TYPE_REMOTE) != 0;
+ }
+
+ /**
* Returns true if the route info has all of the required field.
* A route is valid if and only if it is obtained from
* {@link com.android.server.media.MediaRouterService}.
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
index 074973188c66..ee2cd6f3fcde 100644
--- a/native/android/dynamic_instrumentation_manager.cpp
+++ b/native/android/dynamic_instrumentation_manager.cpp
@@ -73,6 +73,7 @@ ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_Tar
void ADynamicInstrumentationManager_TargetProcess_destroy(
const ADynamicInstrumentationManager_TargetProcess* instance) {
+ if (instance == nullptr) return;
delete instance;
}
@@ -104,6 +105,7 @@ ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifie
void ADynamicInstrumentationManager_MethodDescriptor_destroy(
const ADynamicInstrumentationManager_MethodDescriptor* instance) {
+ if (instance == nullptr) return;
delete instance;
}
@@ -135,6 +137,7 @@ uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOff
void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ if (instance == nullptr) return;
delete instance;
}
diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h
index ab9f37034a22..7bb7615bc3a1 100644
--- a/native/android/include_platform/android/dynamic_instrumentation_manager.h
+++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h
@@ -40,9 +40,12 @@ typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets
*
* @param uid of targeted process.
* @param pid of targeted process.
- * @param processName to disambiguate from corner cases that may arise from pid reuse.
+ * @param processName UTF-8 encoded string representing the same process as specified by `pid`.
+ * Supplied to disambiguate from corner cases that may arise from pid reuse.
+ * Referenced parameter must outlive the returned
+ * ADynamicInstrumentationManager_TargetProcess.
*/
-ADynamicInstrumentationManager_TargetProcess* _Nonnull
+ADynamicInstrumentationManager_TargetProcess* _Nullable
ADynamicInstrumentationManager_TargetProcess_create(
uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36);
/**
@@ -51,22 +54,27 @@ ADynamicInstrumentationManager_TargetProcess* _Nonnull
* @param instance returned from ADynamicInstrumentationManager_TargetProcess_create.
*/
void ADynamicInstrumentationManager_TargetProcess_destroy(
- const ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36);
+ const ADynamicInstrumentationManager_TargetProcess* _Nullable instance) __INTRODUCED_IN(36);
/**
* Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they
- * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy.
+ * are done with ADynamicInstrumentationManager_MethodDescriptor_destroy.
*
- * @param fullyQualifiedClassName fqcn of class containing the method.
- * @param methodName
- * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for
- * primitives.
+ * @param fullyQualifiedClassName UTF-8 encoded fqcn of class containing the method. Referenced
+ * parameter must outlive the returned
+ * ADynamicInstrumentationManager_MethodDescriptor.
+ * @param methodName UTF-8 encoded method name. Referenced parameter must outlive the returned
+ * ADynamicInstrumentationManager_MethodDescriptor.
+ * @param fullyQualifiedParameters UTF-8 encoded fqcn of parameters of the method's signature,
+ * or e.g. "int" for primitives. Referenced parameter should
+ * outlive the returned
+ * ADynamicInstrumentationManager_MethodDescriptor.
* @param numParameters length of `fullyQualifiedParameters` array.
*/
-ADynamicInstrumentationManager_MethodDescriptor* _Nonnull
+ADynamicInstrumentationManager_MethodDescriptor* _Nullable
ADynamicInstrumentationManager_MethodDescriptor_create(
const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName,
- const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters)
+ const char* _Nonnull* _Nonnull fullyQualifiedParameters, size_t numParameters)
__INTRODUCED_IN(36);
/**
* Clean up an ADynamicInstrumentationManager_MethodDescriptor.
@@ -74,14 +82,16 @@ ADynamicInstrumentationManager_MethodDescriptor* _Nonnull
* @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create.
*/
void ADynamicInstrumentationManager_MethodDescriptor_destroy(
- const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance)
+ const ADynamicInstrumentationManager_MethodDescriptor* _Nullable instance)
__INTRODUCED_IN(36);
/**
* Get the containerPath calculated by
* ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
* @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
- * @return The OS path of the containing file.
+ * @return The OS path of the containing file as a UTF-8 string, which has the same lifetime
+ * as the ADynamicInstrumentationManager_ExecutableMethodFileOffsets instance passed
+ * as a param.
*/
const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
@@ -90,7 +100,8 @@ const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets
* Get the containerOffset calculated by
* ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
* @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
- * @return The offset of the containing file within the process' memory.
+ * @return The absolute address of the containing file within remote the process' virtual memory
+ * space.
*/
uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
@@ -98,7 +109,8 @@ uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainer
/**
* Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
* @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
- * @return The offset of the method within the containing file.
+ * @return The offset of the method within the container whose address is returned by
+ * ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset.
*/
uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
@@ -109,7 +121,7 @@ uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOff
* @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
*/
void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
- const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nullable instance)
__INTRODUCED_IN(36);
/**
* Provides ART metadata about the described java method within the target process.
@@ -118,7 +130,9 @@ void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
* @param methodDescriptor describes the targeted method.
* @param out will be populated with the data if successful. A nullptr combined
* with an OK status means that the program method is defined, but the offset
- * info was unavailable because it is not AOT compiled.
+ * info was unavailable because it is not AOT compiled. Caller owns `out` and
+ * should clean it up with
+ * ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy.
* @return status indicating success or failure. The values correspond to the `binder_exception_t`
* enum values from <android/binder_status.h>.
*/
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index e0bc15fe6e94..baae05b4ea03 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -1327,7 +1327,10 @@ public final class CardEmulation {
/**
* This method is called when an AID conflict is detected during an NFC transaction. This
- * can happen when multiple services are registered for the same AID.
+ * can happen when multiple services are registered for the same AID. If your service is
+ * registered for this AID you may want to instruct users to bring your app to the
+ * foreground and ensure you call {@link #setPreferredService(Activity, ComponentName)}
+ * to ensure the transaction is routed to your service.
*
* @param aid The AID that is in conflict
*/
diff --git a/packages/NeuralNetworks/framework/Android.bp b/packages/NeuralNetworks/framework/Android.bp
index 6f45daae0802..af071ba48b1f 100644
--- a/packages/NeuralNetworks/framework/Android.bp
+++ b/packages/NeuralNetworks/framework/Android.bp
@@ -19,10 +19,21 @@ package {
filegroup {
name: "framework-ondeviceintelligence-sources",
srcs: [
- "java/**/*.aidl",
- "java/**/*.java",
+ "module/java/**/*.aidl",
+ "module/java/**/*.java",
+ ],
+ visibility: [
+ "//frameworks/base:__subpackages__",
+ "//packages/modules/NeuralNetworks:__subpackages__",
+ ],
+}
+
+filegroup {
+ name: "framework-ondeviceintelligence-sources-platform",
+ srcs: [
+ "platform/java/**/*.aidl",
+ "platform/java/**/*.java",
],
- path: "java",
visibility: [
"//frameworks/base:__subpackages__",
"//packages/modules/NeuralNetworks:__subpackages__",
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/DownloadCallback.java
index 95fb2888a3e9..95fb2888a3e9 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/DownloadCallback.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.aidl
index 47cfb4a60dc4..47cfb4a60dc4 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.java
index 88f4de2989e4..88f4de2989e4 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/Feature.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.aidl
index c5b3532796cd..c5b3532796cd 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.java
index 063cfb8c321e..063cfb8c321e 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/FeatureDetails.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
index 1fe201f8f1f8..1fe201f8f1f8 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index 2d7ea1a7b016..2d7ea1a7b016 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
index 2e056926e400..2e056926e400 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
index 8688028743d7..8688028743d7 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
index 7e5eb57bbc4a..7e5eb57bbc4a 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index fac5ec6064f8..fac5ec6064f8 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
index 03946eebd40b..03946eebd40b 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
index 6f07693dd39c..6f07693dd39c 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 270b600e2de5..270b600e2de5 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IResponseCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 3e902405f3e0..3e902405f3e0 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
index 958bef0a93e0..958bef0a93e0 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.aidl
index 6f6325408979..6f6325408979 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..cae8db27a435 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
index 2881c9d217dc..2881c9d217dc 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
index 7d35dd7f2237..7d35dd7f2237 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index dc0665a5cea7..dc0665a5cea7 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingCallback.java
index e50d6b1fa97a..e50d6b1fa97a 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingCallback.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingSignal.java
index 733f4fad96f4..733f4fad96f4 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/ProcessingSignal.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index 7ee2af7376ed..7ee2af7376ed 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.aidl
index 599b337fd20f..599b337fd20f 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.java
index 035cc4b365b5..035cc4b365b5 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/TokenInfo.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/utils/BinderUtils.java
index 2916f030e3d0..2916f030e3d0 100644
--- a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/utils/BinderUtils.java
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index cba18c1ef36d..cba18c1ef36d 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 504fdd9b17f9..504fdd9b17f9 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
index 7ead8690abb4..7ead8690abb4 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
index 32a8a6a70406..32a8a6a70406 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
index 253df890b198..253df890b198 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 6907e2bdf2b3..6907e2bdf2b3 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
diff --git a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 315dbaf919e5..315dbaf919e5 100644
--- a/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/framework/module/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/DownloadCallback.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/DownloadCallback.java
new file mode 100644
index 000000000000..95fb2888a3e9
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback functions used for feature downloading via the
+ * {@link OnDeviceIntelligenceManager#requestFeatureDownload}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface DownloadCallback {
+ int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0;
+
+ /**
+ * Sent when feature download could not succeed due to there being no available disk space on
+ * the device.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1;
+
+ /**
+ * Sent when feature download could not succeed due to a network error.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2;
+
+ /**
+ * Sent when feature download has been initiated already, hence no need to request download
+ * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check if
+ * download has been completed.
+ */
+ int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3;
+
+ /**
+ * Sent when feature download did not start due to errors (e.g. remote exception of features not
+ * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check
+ * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}.
+ */
+ int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4;
+
+ /** @hide */
+ @IntDef(value = {
+ DOWNLOAD_FAILURE_STATUS_UNKNOWN,
+ DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE,
+ DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE,
+ DOWNLOAD_FAILURE_STATUS_DOWNLOADING,
+ DOWNLOAD_FAILURE_STATUS_UNAVAILABLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DownloadFailureStatus {
+ }
+
+ /**
+ * Called when model download started properly.
+ *
+ * @param bytesToDownload the total bytes to be downloaded for this {@link Feature}
+ */
+ default void onDownloadStarted(long bytesToDownload) {
+ }
+
+ /**
+ * Called when model download failed.
+ *
+ * @param failureStatus the download failure status
+ * @param errorMessage the error message associated with the download failure
+ */
+ void onDownloadFailed(
+ @DownloadFailureStatus int failureStatus,
+ @Nullable String errorMessage,
+ @NonNull PersistableBundle errorParams);
+
+ /**
+ * Called when model download is in progress.
+ *
+ * @param totalBytesDownloaded the already downloaded bytes for this {@link Feature}
+ */
+ default void onDownloadProgress(long totalBytesDownloaded) {
+ }
+
+ /**
+ * Called when model download is completed. The remote implementation can populate any
+ * associated download params like file stats etc. in this callback to inform the client.
+ *
+ * @param downloadParams params containing info about the completed download.
+ */
+ void onDownloadCompleted(@NonNull PersistableBundle downloadParams);
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.aidl
new file mode 100644
index 000000000000..18494d754674
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable Feature;
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.java
new file mode 100644
index 000000000000..bcc56073e51c
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/Feature.java
@@ -0,0 +1,267 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * Represents a typical feature associated with on-device intelligence.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class Feature implements Parcelable {
+ private final int mId;
+ @Nullable
+ private final String mName;
+ @Nullable
+ private final String mModelName;
+ private final int mType;
+ private final int mVariant;
+ @NonNull
+ private final PersistableBundle mFeatureParams;
+
+ /* package-private */ Feature(
+ int id,
+ @Nullable String name,
+ @Nullable String modelName,
+ int type,
+ int variant,
+ @NonNull PersistableBundle featureParams) {
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ /** Returns the unique and immutable identifier of this feature. */
+ public int getId() {
+ return mId;
+ }
+
+ /** Returns human-readable name of this feature. */
+ public @Nullable String getName() {
+ return mName;
+ }
+
+ /** Returns base model name of this feature. */
+ public @Nullable String getModelName() {
+ return mModelName;
+ }
+
+ /** Returns type identifier of this feature. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Returns variant kind for this feature. */
+ public int getVariant() {
+ return mVariant;
+ }
+
+ public @NonNull PersistableBundle getFeatureParams() {
+ return mFeatureParams;
+ }
+
+ @Override
+ public String toString() {
+ return "Feature { " +
+ "id = " + mId + ", " +
+ "name = " + mName + ", " +
+ "modelName = " + mModelName + ", " +
+ "type = " + mType + ", " +
+ "variant = " + mVariant + ", " +
+ "featureParams = " + mFeatureParams +
+ " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ Feature that = (Feature) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mId == that.mId
+ && java.util.Objects.equals(mName, that.mName)
+ && java.util.Objects.equals(mModelName, that.mModelName)
+ && mType == that.mType
+ && mVariant == that.mVariant
+ && java.util.Objects.equals(mFeatureParams, that.mFeatureParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mModelName);
+ _hash = 31 * _hash + mType;
+ _hash = 31 * _hash + mVariant;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ byte flg = 0;
+ if (mName != null) flg |= 0x2;
+ if (mModelName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mId);
+ if (mName != null) dest.writeString(mName);
+ if (mModelName != null) dest.writeString(mModelName);
+ dest.writeInt(mType);
+ dest.writeInt(mVariant);
+ dest.writeTypedObject(mFeatureParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ /* package-private */ Feature(@NonNull Parcel in) {
+ byte flg = in.readByte();
+ int id = in.readInt();
+ String name = (flg & 0x2) == 0 ? null : in.readString();
+ String modelName = (flg & 0x4) == 0 ? null : in.readString();
+ int type = in.readInt();
+ int variant = in.readInt();
+ PersistableBundle featureParams = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<Feature> CREATOR
+ = new Parcelable.Creator<Feature>() {
+ @Override
+ public Feature[] newArray(int size) {
+ return new Feature[size];
+ }
+
+ @Override
+ public Feature createFromParcel(@NonNull Parcel in) {
+ return new Feature(in);
+ }
+ };
+
+ /**
+ * A builder for {@link Feature}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+ private int mId;
+ private @Nullable String mName;
+ private @Nullable String mModelName;
+ private int mType;
+ private int mVariant;
+ private @NonNull PersistableBundle mFeatureParams;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Provides a builder instance to create a feature for given id.
+ * @param id the unique identifier for the feature.
+ */
+ public Builder(int id) {
+ mId = id;
+ mFeatureParams = new PersistableBundle();
+ }
+
+ public @NonNull Builder setName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mName = value;
+ return this;
+ }
+
+ public @NonNull Builder setModelName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mModelName = value;
+ return this;
+ }
+
+ public @NonNull Builder setType(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mType = value;
+ return this;
+ }
+
+ public @NonNull Builder setVariant(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mVariant = value;
+ return this;
+ }
+
+ public @NonNull Builder setFeatureParams(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mFeatureParams = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Feature build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ Feature o = new Feature(
+ mId,
+ mName,
+ mModelName,
+ mType,
+ mVariant,
+ mFeatureParams);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.aidl
new file mode 100644
index 000000000000..0589bf8bacb9
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable FeatureDetails;
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.java
new file mode 100644
index 000000000000..0ee0cc34c512
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -0,0 +1,178 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.text.MessageFormat;
+
+/**
+ * Represents a status of a requested {@link Feature}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class FeatureDetails implements Parcelable {
+ @Status
+ private final int mFeatureStatus;
+ @NonNull
+ private final PersistableBundle mFeatureDetailParams;
+
+ /** Invalid or unavailable {@code AiFeature}. */
+ public static final int FEATURE_STATUS_UNAVAILABLE = 0;
+
+ /** Feature can be downloaded on request. */
+ public static final int FEATURE_STATUS_DOWNLOADABLE = 1;
+
+ /** Feature is being downloaded. */
+ public static final int FEATURE_STATUS_DOWNLOADING = 2;
+
+ /** Feature is fully downloaded and ready to use. */
+ public static final int FEATURE_STATUS_AVAILABLE = 3;
+
+ /** Underlying service is unavailable and feature status cannot be fetched. */
+ public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ FEATURE_STATUS_UNAVAILABLE,
+ FEATURE_STATUS_DOWNLOADABLE,
+ FEATURE_STATUS_DOWNLOADING,
+ FEATURE_STATUS_AVAILABLE,
+ FEATURE_STATUS_SERVICE_UNAVAILABLE
+ })
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ public FeatureDetails(
+ @Status int featureStatus,
+ @NonNull PersistableBundle featureDetailParams) {
+ this.mFeatureStatus = featureStatus;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mFeatureStatus);
+ this.mFeatureDetailParams = featureDetailParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+ public FeatureDetails(
+ @Status int featureStatus) {
+ this.mFeatureStatus = featureStatus;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mFeatureStatus);
+ this.mFeatureDetailParams = new PersistableBundle();
+ }
+
+
+ /**
+ * Returns an integer value associated with the feature status.
+ */
+ public @Status int getFeatureStatus() {
+ return mFeatureStatus;
+ }
+
+
+ /**
+ * Returns a persistable bundle contain any additional status related params.
+ */
+ public @NonNull PersistableBundle getFeatureDetailParams() {
+ return mFeatureDetailParams;
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ + "persistableBundle = {1} '}'",
+ mFeatureStatus,
+ mFeatureDetailParams);
+ }
+
+ @Override
+ public boolean equals(@android.annotation.Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FeatureDetails that = (FeatureDetails) o;
+ return mFeatureStatus == that.mFeatureStatus
+ && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mFeatureStatus;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mFeatureStatus);
+ dest.writeTypedObject(mFeatureDetailParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ FeatureDetails(@NonNull android.os.Parcel in) {
+ int status = in.readInt();
+ PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mFeatureStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mFeatureStatus);
+ this.mFeatureDetailParams = persistableBundle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+
+ public static final @NonNull Parcelable.Creator<FeatureDetails> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public FeatureDetails[] newArray(int size) {
+ return new FeatureDetails[size];
+ }
+
+ @Override
+ public FeatureDetails createFromParcel(@NonNull android.os.Parcel in) {
+ return new FeatureDetails(in);
+ }
+ };
+
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ICancellationSignal.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
new file mode 100644
index 000000000000..1fe201f8f1f8
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+oneway interface ICancellationSignal {
+ void cancel();
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
new file mode 100644
index 000000000000..2d7ea1a7b016
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+ * Interface for Download callback to be passed onto service implementation,
+ *
+ * @hide
+ */
+oneway interface IDownloadCallback {
+ void onDownloadStarted(long bytesToDownload) = 1;
+ void onDownloadProgress(long bytesDownloaded) = 2;
+ void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDownloadCompleted(in PersistableBundle downloadParams) = 4;
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
new file mode 100644
index 000000000000..2e056926e400
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving a feature for the given identifier.
+ *
+ * @hide
+ */
+oneway interface IFeatureCallback {
+ void onSuccess(in Feature result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
new file mode 100644
index 000000000000..8688028743d7
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving details about a given feature. .
+ *
+ * @hide
+ */
+oneway interface IFeatureDetailsCallback {
+ void onSuccess(in FeatureDetails result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
new file mode 100644
index 000000000000..7e5eb57bbc4a
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
@@ -0,0 +1,15 @@
+package android.app.ondeviceintelligence;
+
+import java.util.List;
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving list of supported features.
+ *
+ * @hide
+ */
+oneway interface IListFeaturesCallback {
+ void onSuccess(in List<Feature> result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
new file mode 100644
index 000000000000..1977a3923578
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.app.ondeviceintelligence;
+
+ import com.android.internal.infra.AndroidFuture;
+ import android.os.ICancellationSignal;
+ import android.os.ParcelFileDescriptor;
+ import android.os.PersistableBundle;
+ import android.os.RemoteCallback;
+ import android.os.Bundle;
+ import android.app.ondeviceintelligence.Feature;
+ import android.app.ondeviceintelligence.FeatureDetails;
+ import android.app.ondeviceintelligence.InferenceInfo;
+ import java.util.List;
+ import android.app.ondeviceintelligence.IDownloadCallback;
+ import android.app.ondeviceintelligence.IListFeaturesCallback;
+ import android.app.ondeviceintelligence.IFeatureCallback;
+ import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+ import android.app.ondeviceintelligence.IResponseCallback;
+ import android.app.ondeviceintelligence.IStreamingResponseCallback;
+ import android.app.ondeviceintelligence.IProcessingSignal;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
+
+
+ /**
+ * Interface for a OnDeviceIntelligenceManager for managing OnDeviceIntelligenceService and OnDeviceSandboxedInferenceService.
+ *
+ * @hide
+ */
+interface IOnDeviceIntelligenceManager {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getVersion(in RemoteCallback remoteCallback) = 1;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeature(in int featureId, in IFeatureCallback remoteCallback) = 2;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void listFeatures(in IListFeaturesCallback listFeaturesCallback) = 3;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestFeatureDownload(in Feature feature, in AndroidFuture cancellationSignalFuture, in IDownloadCallback callback) = 5;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestTokenInfo(in Feature feature, in Bundle requestBundle, in AndroidFuture cancellationSignalFuture,
+ in ITokenInfoCallback tokenInfocallback) = 6;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequest(in Feature feature, in Bundle requestBundle, int requestType,
+ in AndroidFuture cancellationSignalFuture,
+ in AndroidFuture processingSignalFuture,
+ in IResponseCallback responseCallback) = 7;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequestStreaming(in Feature feature,
+ in Bundle requestBundle, int requestType, in AndroidFuture cancellationSignalFuture,
+ in AndroidFuture processingSignalFuture,
+ in IStreamingResponseCallback streamingCallback) = 8;
+
+ String getRemoteServicePackageName() = 9;
+
+ List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) = 10;
+ }
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
new file mode 100644
index 000000000000..03946eebd40b
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+* Signal to provide to the remote implementation in context of a given request or
+* feature specific event.
+*
+* @hide
+*/
+
+oneway interface IProcessingSignal {
+ void sendSignal(in PersistableBundle actionParams) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IRemoteCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
new file mode 100644
index 000000000000..6f07693dd39c
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
@@ -0,0 +1,24 @@
+/*
+* Copyright 2024, The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.app.ondeviceintelligence;
+
+import android.os.Bundle;
+
+/* @hide */
+oneway interface IRemoteCallback {
+ void sendResult(in Bundle data);
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IResponseCallback.aidl
new file mode 100644
index 000000000000..270b600e2de5
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -0,0 +1,16 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+/**
+ * Interface for a IResponseCallback for receiving response from on-device intelligence service.
+ *
+ * @hide
+ */
+oneway interface IResponseCallback {
+ void onSuccess(in Bundle resultBundle) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
new file mode 100644
index 000000000000..3e902405f3e0
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -0,0 +1,18 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.Bundle;
+
+
+/**
+ * This callback is a streaming variant of {@link IResponseCallback}.
+ *
+ * @hide
+ */
+oneway interface IStreamingResponseCallback {
+ void onNewContent(in Bundle processedResult) = 1;
+ void onSuccess(in Bundle result) = 2;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 000000000000..958bef0a93e0
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+ * Interface for receiving the token info of a request for a given feature.
+ *
+ * @hide
+ */
+oneway interface ITokenInfoCallback {
+ void onSuccess(in TokenInfo tokenInfo) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.aidl
new file mode 100644
index 000000000000..6d70fc4577a2
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable InferenceInfo;
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
new file mode 100644
index 000000000000..cae8db27a435
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents the information related to an inference event to track the resource usage
+ * as a function of inference time.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
+public final class InferenceInfo implements Parcelable {
+
+ /**
+ * Uid for the caller app.
+ */
+ private final int uid;
+
+ /**
+ * Inference start time (milliseconds from the epoch time).
+ */
+ private final long startTimeMs;
+
+ /**
+ * Inference end time (milliseconds from the epoch time).
+ */
+ private final long endTimeMs;
+
+ /**
+ * Suspended time in milliseconds.
+ */
+ private final long suspendedTimeMs;
+
+ /**
+ * Constructs an InferenceInfo object with the specified parameters.
+ *
+ * @param uid Uid for the caller app.
+ * @param startTimeMs Inference start time (milliseconds from the epoch time).
+ * @param endTimeMs Inference end time (milliseconds from the epoch time).
+ * @param suspendedTimeMs Suspended time in milliseconds.
+ */
+ InferenceInfo(int uid, long startTimeMs, long endTimeMs,
+ long suspendedTimeMs) {
+ this.uid = uid;
+ this.startTimeMs = startTimeMs;
+ this.endTimeMs = endTimeMs;
+ this.suspendedTimeMs = suspendedTimeMs;
+ }
+
+ /**
+ * Constructs an InferenceInfo object from a Parcel.
+ *
+ * @param in The Parcel to read the object's data from.
+ */
+ private InferenceInfo(@NonNull Parcel in) {
+ uid = in.readInt();
+ startTimeMs = in.readLong();
+ endTimeMs = in.readLong();
+ suspendedTimeMs = in.readLong();
+ }
+
+
+ /**
+ * Writes the object's data to the provided Parcel.
+ *
+ * @param dest The Parcel to write the object's data to.
+ * @param flags Additional flags about how the object should be written.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(uid);
+ dest.writeLong(startTimeMs);
+ dest.writeLong(endTimeMs);
+ dest.writeLong(suspendedTimeMs);
+ }
+
+ /**
+ * Returns the UID for the caller app.
+ *
+ * @return the UID for the caller app.
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * Returns the inference start time in milliseconds from the epoch time.
+ *
+ * @return the inference start time in milliseconds from the epoch time.
+ */
+ @CurrentTimeMillisLong
+ public long getStartTimeMillis() {
+ return startTimeMs;
+ }
+
+ /**
+ * Returns the inference end time in milliseconds from the epoch time.
+ *
+ * @return the inference end time in milliseconds from the epoch time.
+ */
+ @CurrentTimeMillisLong
+ public long getEndTimeMillis() {
+ return endTimeMs;
+ }
+
+ /**
+ * Returns the suspended time in milliseconds.
+ *
+ * @return the suspended time in milliseconds.
+ */
+ @CurrentTimeMillisLong
+ public long getSuspendedTimeMillis() {
+ return suspendedTimeMs;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InferenceInfo> CREATOR
+ = new Parcelable.Creator<InferenceInfo>() {
+ @Override
+ public InferenceInfo[] newArray(int size) {
+ return new InferenceInfo[size];
+ }
+
+ @Override
+ public InferenceInfo createFromParcel(@android.annotation.NonNull Parcel in) {
+ return new InferenceInfo(in);
+ }
+ };
+
+ /**
+ * Builder class for creating instances of {@link InferenceInfo}.
+ */
+ public static final class Builder {
+ private final int uid;
+ private long startTimeMs;
+ private long endTimeMs;
+ private long suspendedTimeMs;
+
+ /**
+ * Provides a builder instance to create a InferenceInfo for given caller uid.
+ *
+ * @param uid the caller uid associated with the inference info.
+ */
+ public Builder(int uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * Sets the inference start time in milliseconds from the epoch time.
+ *
+ * @param startTimeMs the inference start time in milliseconds from the epoch time.
+ * @return the Builder instance.
+ */
+ public @NonNull Builder setStartTimeMillis(@CurrentTimeMillisLong long startTimeMs) {
+ this.startTimeMs = startTimeMs;
+ return this;
+ }
+
+ /**
+ * Sets the inference end time in milliseconds from the epoch time.
+ *
+ * @param endTimeMs the inference end time in milliseconds from the epoch time.
+ * @return the Builder instance.
+ */
+ public @NonNull Builder setEndTimeMillis(@CurrentTimeMillisLong long endTimeMs) {
+ this.endTimeMs = endTimeMs;
+ return this;
+ }
+
+ /**
+ * Sets the suspended time in milliseconds.
+ *
+ * @param suspendedTimeMs the suspended time in milliseconds.
+ * @return the Builder instance.
+ */
+ public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ this.suspendedTimeMs = suspendedTimeMs;
+ return this;
+ }
+
+ /**
+ * Builds and returns an instance of {@link InferenceInfo}.
+ *
+ * @return an instance of {@link InferenceInfo}.
+ */
+ public @NonNull InferenceInfo build() {
+ return new InferenceInfo(uid, startTimeMs, endTimeMs,
+ suspendedTimeMs);
+ }
+ }
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 000000000000..2881c9d217dc
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,200 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Exception type to be used for errors related to on-device intelligence system service with
+ * appropriate error code.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceException extends Exception {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100;
+
+ /**
+ * The connection to remote service failed and the processing state could not be updated.
+ */
+ public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200;
+
+
+ /**
+ * Error code associated with the on-device intelligence failure.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+ })
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OnDeviceIntelligenceError {
+ }
+
+ private final int mErrorCode;
+ private final PersistableBundle mErrorParams;
+
+ /** Returns the error code of the exception. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error params of the exception. */
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return mErrorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code, error message and
+ * error params.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error params.
+ *
+ * @param errorCode The error code.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error message.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code.
+ *
+ * @param errorCode The error code.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
new file mode 100644
index 000000000000..7d35dd7f2237
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for OnDeviceIntelligence service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceFrameworkInitializer {
+ private OnDeviceIntelligenceFrameworkInitializer() {
+ }
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers
+ * OnDeviceIntelligence service to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides {@link
+ * SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(Context.ON_DEVICE_INTELLIGENCE_SERVICE,
+ OnDeviceIntelligenceManager.class,
+ (context, serviceBinder) -> {
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(serviceBinder);
+ return new OnDeviceIntelligenceManager(context, manager);
+ });
+ }
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
new file mode 100644
index 000000000000..78cf1d7a611b
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -0,0 +1,642 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.LongConsumer;
+
+/**
+ * Allows granted apps to manage on-device intelligence service configured on the device. Typical
+ * calling pattern will be to query and setup a required feature before proceeding to request
+ * processing.
+ *
+ * The contracts in this Manager class are designed to be open-ended in general, to allow
+ * interoperability. Therefore, it is recommended that implementations of this system-service
+ * expose this API to the clients via a separate sdk or library which has more defined contract.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class OnDeviceIntelligenceManager {
+ /**
+ * @hide
+ */
+ public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+ /**
+ * @hide
+ */
+ public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+ "AugmentRequestContentBundleKey";
+
+ private static final String TAG = "OnDeviceIntelligence";
+ private final Context mContext;
+ private final IOnDeviceIntelligenceManager mService;
+
+ /**
+ * @hide
+ */
+ public OnDeviceIntelligenceManager(Context context, IOnDeviceIntelligenceManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Asynchronously get the version of the underlying remote implementation.
+ *
+ * @param versionConsumer consumer to populate the version of remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getVersion(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull LongConsumer versionConsumer) {
+ try {
+ RemoteCallback callback = new RemoteCallback(result -> {
+ if (result == null) {
+ Binder.withCleanCallingIdentity(
+ () -> callbackExecutor.execute(() -> versionConsumer.accept(0)));
+ }
+ long version = result.getLong(API_VERSION_BUNDLE_KEY);
+ Binder.withCleanCallingIdentity(
+ () -> callbackExecutor.execute(() -> versionConsumer.accept(version)));
+ });
+ mService.getVersion(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Get package name configured for providing the remote implementation for this system service.
+ */
+ @Nullable
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public String getRemoteServicePackageName() {
+ String result;
+ try {
+ result = mService.getRemoteServicePackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Asynchronously get feature for a given id.
+ *
+ * @param featureId the identifier pointing to the feature.
+ * @param featureReceiver callback to populate the feature object for given identifier.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeature(
+ int featureId,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
+ try {
+ IFeatureCallback callback =
+ new IFeatureCallback.Stub() {
+ @Override
+ public void onSuccess(Feature result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.getFeature(featureId, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Asynchronously get a list of features that are supported for the caller.
+ *
+ * @param featureListReceiver callback to populate the list of features.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void listFeatures(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
+ try {
+ IListFeaturesCallback callback =
+ new IListFeaturesCallback.Stub() {
+ @Override
+ public void onSuccess(List<Feature> result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.listFeatures(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method should be used to fetch details about a feature which need some additional
+ * computation, that can be inefficient to return in all calls to {@link #getFeature}. Callers
+ * and implementation can utilize the {@link Feature#getFeatureParams()} to pass hint on what
+ * details are expected by the caller.
+ *
+ * @param feature the feature to check status for.
+ * @param featureDetailsReceiver callback to populate the feature details to.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeatureDetails(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
+ try {
+ IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
+
+ @Override
+ public void onSuccess(FeatureDetails result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onError(
+ new OnDeviceIntelligenceException(errorCode,
+ errorMessage, errorParams))));
+ }
+ };
+ mService.getFeatureDetails(feature, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method handles downloading all model and config files required to process requests
+ * sent against a given feature. The caller can listen to updates on the download status via
+ * the callback.
+ *
+ * Note: If a feature was already requested for downloaded previously, the onDownloadFailed
+ * callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
+ * In such cases, clients should query the feature status via {@link #getFeatureDetails} to
+ * check on the feature's download status.
+ *
+ * @param feature feature to request download for.
+ * @param callback callback to populate updates about download status.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestFeatureDownload(@NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull DownloadCallback callback) {
+ try {
+ IDownloadCallback downloadCallback = new IDownloadCallback.Stub() {
+
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadStarted(bytesToDownload)));
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesDownloaded) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadProgress(bytesDownloaded)));
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadFailed(failureStatus, errorMessage,
+ errorParams)));
+ }
+
+ @Override
+ public void onDownloadCompleted(PersistableBundle downloadParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadCompleted(downloadParams)));
+ }
+ };
+
+ mService.requestFeatureDownload(feature,
+ configureRemoteCancellationFuture(cancellationSignal, callbackExecutor),
+ downloadCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * The methods computes the token related information for a given request payload using the
+ * provided {@link Feature}.
+ *
+ * @param feature feature associated with the request.
+ * @param request request and associated params represented by the Bundle
+ * data.
+ * @param outcomeReceiver callback to populate the token info or exception in case of
+ * failure.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<TokenInfo,
+ OnDeviceIntelligenceException> outcomeReceiver) {
+ try {
+ ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
+ @Override
+ public void onSuccess(TokenInfo tokenInfo) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onResult(tokenInfo)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+
+ mService.requestTokenInfo(feature, request,
+ configureRemoteCancellationFuture(cancellationSignal, callbackExecutor),
+ callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Asynchronously Process a request based on the associated params, to populate a
+ * response in
+ * {@link OutcomeReceiver#onResult} callback or failure callback status code if there
+ * was a
+ * failure.
+ *
+ * @param feature feature associated with the request.
+ * @param request request and associated params represented by the Bundle
+ * data.
+ * @param requestType type of request being sent for processing the content.
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
+ * remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ * @param processingCallback callback to populate the response content and
+ * associated params.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ProcessingCallback processingCallback) {
+ try {
+ IResponseCallback callback = new IResponseCallback.Stub() {
+ @Override
+ public void onSuccess(@InferenceParams Bundle result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(() -> processingCallback.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> processingCallback.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage, errorParams))));
+ }
+
+ @Override
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> processingCallback.onDataAugmentRequest(request, result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+ callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+ })));
+ }
+ };
+
+
+ mService.processRequest(feature, request, requestType,
+ configureRemoteCancellationFuture(cancellationSignal, callbackExecutor),
+ configureRemoteProcessingSignalFuture(processingSignal, callbackExecutor),
+ callback);
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Variation of {@link #processRequest} that asynchronously processes a request in a
+ * streaming
+ * fashion, where new content is pushed to caller in chunks via the
+ * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete,
+ * the service should call {@link StreamingProcessingCallback#onResult} and can optionally
+ * populate the complete the full response {@link Bundle} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the contents already
+ * streamed.
+ *
+ * @param feature feature associated with the request.
+ * @param request request and associated params represented by the Bundle
+ * data.
+ * @param requestType type of request being sent for processing the content.
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
+ * remote implementation.
+ * @param streamingProcessingCallback streaming callback to populate the response content and
+ * associated params.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void processRequestStreaming(@NonNull Feature feature,
+ @NonNull @InferenceParams Bundle request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull StreamingProcessingCallback streamingProcessingCallback) {
+ try {
+ IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
+ @Override
+ public void onNewContent(@InferenceParams Bundle result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingProcessingCallback.onPartialResult(result));
+ });
+ }
+
+ @Override
+ public void onSuccess(@InferenceParams Bundle result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingProcessingCallback.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingProcessingCallback.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage, errorParams)));
+ });
+ }
+
+
+ @Override
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> streamingProcessingCallback.onDataAugmentRequest(content,
+ contentResponse -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ contentResponse);
+ callbackExecutor.execute(
+ () -> contentCallback.sendResult(bundle));
+ })));
+ }
+ };
+
+ mService.processRequestStreaming(
+ feature, request, requestType,
+ configureRemoteCancellationFuture(cancellationSignal, callbackExecutor),
+ configureRemoteProcessingSignalFuture(processingSignal, callbackExecutor),
+ callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This is primarily intended to be used to attribute/blame on-device intelligence power usage,
+ * via the configured remote implementation, to its actual caller.
+ *
+ * @param startTimeEpochMillis epoch millis used to filter the InferenceInfo events.
+ * @return InferenceInfo events since the passed in startTimeEpochMillis.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
+ public @NonNull List<InferenceInfo> getLatestInferenceInfo(@CurrentTimeMillisLong long startTimeEpochMillis) {
+ try {
+ return mService.getLatestInferenceInfo(startTimeEpochMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /** Request inference with provided Bundle and Params. */
+ public static final int REQUEST_TYPE_INFERENCE = 0;
+
+ /**
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc
+ * .which
+ * are time consuming beforehand to remove overhead and allow quick processing of requests
+ * thereof.
+ */
+ public static final int REQUEST_TYPE_PREPARE = 1;
+
+ /** Request Embeddings of the passed-in Bundle. */
+ public static final int REQUEST_TYPE_EMBEDDINGS = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ REQUEST_TYPE_INFERENCE,
+ REQUEST_TYPE_PREPARE,
+ REQUEST_TYPE_EMBEDDINGS
+ })
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+ ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestType {
+ }
+
+ /**
+ * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+ * when passed via Binder IPC. Following restrictions apply :
+ * <ul>
+ * <li> {@link PersistableBundle}s are allowed.</li>
+ * <li> Any primitive types or their collections can be added as usual.</li>
+ * <li>IBinder objects should *not* be added.</li>
+ * <li>Parcelable data which has no active-objects, should be added as
+ * {@link Bundle#putByteArray}</li>
+ * <li>Parcelables have active-objects, only following types will be allowed</li>
+ * <ul>
+ * <li>{@link android.os.ParcelFileDescriptor} opened in
+ * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li>
+ * </ul>
+ * </ul>
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
+ */
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface StateParams {
+ }
+
+ /**
+ * This is an extension of {@link StateParams} but for purpose of inference few other types are
+ * also allowed as read-only, as listed below.
+ *
+ * <li>{@link Bitmap} set as immutable.</li>
+ * <li>{@link android.database.CursorWindow}</li>
+ * <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li>
+ * </ul>
+ * </ul>
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
+ */
+ @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE_USE})
+ public @interface InferenceParams {
+ }
+
+ /**
+ * This is an extension of {@link StateParams} with the exception that it allows writing
+ * {@link Bitmap} as part of the response.
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
+ */
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface ResponseParams {
+ }
+
+ @Nullable
+ private static AndroidFuture<IBinder> configureRemoteCancellationFuture(
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Executor callbackExecutor) {
+ if (cancellationSignal == null) {
+ return null;
+ }
+ AndroidFuture<IBinder> cancellationFuture = new AndroidFuture<>();
+ cancellationFuture.whenCompleteAsync(
+ (cancellationTransport, error) -> {
+ if (error != null || cancellationTransport == null) {
+ Log.e(TAG, "Unable to receive the remote cancellation signal.", error);
+ } else {
+ cancellationSignal.setRemote(
+ ICancellationSignal.Stub.asInterface(cancellationTransport));
+ }
+ }, callbackExecutor);
+ return cancellationFuture;
+ }
+
+ @Nullable
+ private static AndroidFuture<IBinder> configureRemoteProcessingSignalFuture(
+ ProcessingSignal processingSignal, Executor executor) {
+ if (processingSignal == null) {
+ return null;
+ }
+ AndroidFuture<IBinder> processingSignalFuture = new AndroidFuture<>();
+ processingSignalFuture.whenCompleteAsync(
+ (transport, error) -> {
+ if (error != null || transport == null) {
+ Log.e(TAG, "Unable to receive the remote processing signal.", error);
+ } else {
+ processingSignal.setRemote(IProcessingSignal.Stub.asInterface(transport));
+ }
+ }, executor);
+ return processingSignalFuture;
+ }
+
+
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingCallback.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 000000000000..e50d6b1fa97a
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ResponseParams;
+
+import java.util.function.Consumer;
+
+/**
+ * Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial response that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingCallback {
+ /**
+ * Invoked when request has been processed and result is ready to be propagated to the
+ * caller.
+ *
+ * @param result Response to be passed as a result.
+ */
+ void onResult(@NonNull @ResponseParams Bundle result);
+
+ /**
+ * Called when the request processing fails. The failure details are indicated by the
+ * {@link OnDeviceIntelligenceException} passed as an argument to this method.
+ *
+ * @param error An exception with more details about the error that occurred.
+ */
+ void onError(@NonNull OnDeviceIntelligenceException error);
+
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param processedContent The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request. Bundle passed in here is
+ * expected to be non-null or EMPTY when there is no response.
+ */
+ default void onDataAugmentRequest(
+ @NonNull @ResponseParams Bundle processedContent,
+ @NonNull Consumer<@InferenceParams Bundle> contentConsumer) {
+ contentConsumer.accept(Bundle.EMPTY);
+ }
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingSignal.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingSignal.java
new file mode 100644
index 000000000000..733f4fad96f4
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/ProcessingSignal.java
@@ -0,0 +1,225 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A signal to perform orchestration actions on the inference and optionally receive a output about
+ * the result of the signal. This is an extension of {@link android.os.CancellationSignal}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class ProcessingSignal {
+ private final Object mLock = new Object();
+
+ private static final int MAX_QUEUE_SIZE = 10;
+
+ @GuardedBy("mLock")
+ private final ArrayDeque<PersistableBundle> mActionParamsQueue;
+
+ @GuardedBy("mLock")
+ private IProcessingSignal mRemote;
+
+ private OnProcessingSignalCallback mOnProcessingSignalCallback;
+ private Executor mExecutor;
+
+ public ProcessingSignal() {
+ mActionParamsQueue = new ArrayDeque<>(MAX_QUEUE_SIZE);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when processing signals are received.
+ */
+ public interface OnProcessingSignalCallback {
+ /**
+ * Called when a custom signal was received.
+ * This method allows the receiver to provide logic to be executed based on the signal
+ * received.
+ *
+ * @param actionParams Parameters for the signal in the form of a {@link PersistableBundle}.
+ */
+
+ void onSignalReceived(@NonNull PersistableBundle actionParams);
+ }
+
+
+ /**
+ * Sends a custom signal with the provided parameters. If there are multiple concurrent
+ * requests to this method, the actionParams are queued in a blocking fashion, in the order they
+ * are received.
+ *
+ * It also signals the remote callback
+ * with the same params if already configured, if not the action is queued to be sent when a
+ * remote is configured. Similarly, on the receiver side, the callback will be invoked if
+ * already set, if not all actions are queued to be sent to callback when it is set.
+ *
+ * @param actionParams Parameters for the signal.
+ */
+ public void sendSignal(@NonNull PersistableBundle actionParams) {
+ final OnProcessingSignalCallback callback;
+ final IProcessingSignal remote;
+ synchronized (mLock) {
+ if (mActionParamsQueue.size() > MAX_QUEUE_SIZE) {
+ throw new RuntimeException(
+ "Maximum actions that can be queued are : " + MAX_QUEUE_SIZE);
+ }
+
+ mActionParamsQueue.add(actionParams);
+ callback = mOnProcessingSignalCallback;
+ remote = mRemote;
+
+ if (callback != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(
+ () -> callback.onSignalReceived(params));
+ }
+ }
+ if (remote != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Sets the processing signal callback to be called when signals are received.
+ *
+ * This method is intended to be used by the recipient of a processing signal
+ * such as the remote implementation in
+ * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} to handle
+ * processing signals while performing a long-running operation. This method is not
+ * intended to be used by the caller themselves.
+ *
+ * If {@link ProcessingSignal#sendSignal} has already been called, then the provided callback
+ * is invoked immediately and all previously queued actions are passed to remote signal.
+ *
+ * This method is guaranteed that the callback will not be called after it
+ * has been removed.
+ *
+ * @param callback The processing signal callback, or null to remove the current callback.
+ * @param executor Executor to the run the callback methods on.
+ */
+ public void setOnProcessingSignalCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable OnProcessingSignalCallback callback) {
+ Objects.requireNonNull(executor);
+ synchronized (mLock) {
+ if (mOnProcessingSignalCallback == callback) {
+ return;
+ }
+
+ mOnProcessingSignalCallback = callback;
+ mExecutor = executor;
+ if (callback == null || mActionParamsQueue.isEmpty()) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(() -> callback.onSignalReceived(params));
+ }
+ }
+ }
+
+ /**
+ * Sets the remote transport.
+ *
+ * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also
+ * sequentially sent to the configured remote.
+ *
+ * This method guarantees that the remote transport will not be called after it
+ * has been removed.
+ *
+ * @param remote The remote transport, or null to remove.
+ * @hide
+ */
+ void setRemote(IProcessingSignal remote) {
+ synchronized (mLock) {
+ mRemote = remote;
+ if (mActionParamsQueue.isEmpty() || remote == null) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to send action to remote signal", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a transport that can be returned back to the caller of
+ * a Binder function and subsequently used to dispatch a processing signal.
+ *
+ * @return The new processing signal transport.
+ * @hide
+ */
+ public static IProcessingSignal createTransport() {
+ return new Transport();
+ }
+
+ /**
+ * Given a locally created transport, returns its associated processing signal.
+ *
+ * @param transport The locally created transport, or null if none.
+ * @return The associated processing signal, or null if none.
+ * @hide
+ */
+ public static ProcessingSignal fromTransport(IProcessingSignal transport) {
+ if (transport instanceof Transport) {
+ return ((Transport) transport).mProcessingSignal;
+ }
+ return null;
+ }
+
+ private static final class Transport extends IProcessingSignal.Stub {
+ final ProcessingSignal mProcessingSignal = new ProcessingSignal();
+
+ @Override
+ public void sendSignal(PersistableBundle actionParams) {
+ mProcessingSignal.sendSignal(actionParams);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
new file mode 100644
index 000000000000..7ee2af7376ed
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/StreamingProcessingCallback.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 android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ResponseParams;
+
+/**
+ * Streaming variant of {@link ProcessingCallback} to populate response while processing a given
+ * request, possibly in chunks to provide a async processing behaviour to the caller.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface StreamingProcessingCallback extends ProcessingCallback {
+ /**
+ * Callback that would be invoked when a part of the response i.e. some response is
+ * already processed, and needs to be passed onto the caller.
+ */
+ void onPartialResult(@NonNull @ResponseParams Bundle partialResult);
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.aidl b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.aidl
new file mode 100644
index 000000000000..2c19c1eb246c
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable TokenInfo;
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 000000000000..035cc4b365b5
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+ private final long mCount;
+ private final PersistableBundle mInfoParams;
+
+ /**
+ * Construct a token count using the count value and associated params.
+ */
+ public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+ this.mCount = count;
+ mInfoParams = persistableBundle;
+ }
+
+ /**
+ * Construct a token count using the count value.
+ */
+ public TokenInfo(long count) {
+ this.mCount = count;
+ this.mInfoParams = new PersistableBundle();
+ }
+
+ /**
+ * Returns the token count associated with a request payload.
+ */
+ public long getCount() {
+ return mCount;
+ }
+
+ /**
+ * Returns the params representing token info.
+ */
+ @NonNull
+ public PersistableBundle getInfoParams() {
+ return mInfoParams;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mCount);
+ dest.writePersistableBundle(mInfoParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+ = new Parcelable.Creator<>() {
+ @Override
+ public TokenInfo[] newArray(int size) {
+ return new TokenInfo[size];
+ }
+
+ @Override
+ public TokenInfo createFromParcel(@NonNull Parcel in) {
+ return new TokenInfo(in.readLong(), in.readPersistableBundle());
+ }
+ };
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/utils/BinderUtils.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/utils/BinderUtils.java
new file mode 100644
index 000000000000..2916f030e3d0
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/utils/BinderUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.app.ondeviceintelligence.utils;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+
+import java.util.function.Supplier;
+
+/**
+ * Collection of utilities for {@link Binder} and related classes.
+ * @hide
+ */
+public class BinderUtils {
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+ *
+ * Any exception thrown by the given action will be caught and rethrown after the call to
+ * {@link Binder#restoreCallingIdentity}
+ *
+ * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+ * since it is not public.
+ *
+ * @hide
+ */
+ public static final <T extends Exception> void withCleanCallingIdentity(
+ @NonNull ThrowingRunnable<T> action) throws T {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ action.run();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Like a Runnable, but declared to throw an exception.
+ *
+ * @param <T> The exception class which is declared to be thrown.
+ */
+ @FunctionalInterface
+ public interface ThrowingRunnable<T extends Exception> {
+ /** @see java.lang.Runnable */
+ void run() throws T;
+ }
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the
+ * result.
+ *
+ * <p>Any exception thrown by the given action will be caught and rethrown after
+ * the call to {@link Binder#restoreCallingIdentity}.
+ *
+ * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+ * since it is not public.
+ *
+ * @hide
+ */
+ public static final <T, E extends Exception> T withCleanCallingIdentity(
+ @NonNull ThrowingSupplier<T, E> action) throws E {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return action.get();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * An equivalent of {@link Supplier}
+ *
+ * @param <T> The class which is declared to be returned.
+ * @param <E> The exception class which is declared to be thrown.
+ */
+ @FunctionalInterface
+ public interface ThrowingSupplier<T, E extends Exception> {
+ /** @see java.util.function.Supplier */
+ T get() throws E;
+ }
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
new file mode 100644
index 000000000000..45c43502d6de
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ICancellationSignal;
+import android.os.RemoteCallback;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import com.android.internal.infra.AndroidFuture;
+import android.service.ondeviceintelligence.IRemoteProcessingService;
+
+
+/**
+ * Interface for a concrete implementation to provide on device intelligence services.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceIntelligenceService {
+ void getVersion(in RemoteCallback remoteCallback);
+ void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+ void requestFeatureDownload(int callerUid, in Feature feature,
+ in AndroidFuture cancellationSignal,
+ in IDownloadCallback downloadCallback);
+ void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+ void notifyInferenceServiceConnected();
+ void notifyInferenceServiceDisconnected();
+ void ready();
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
new file mode 100644
index 000000000000..1af3b0f374f1
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.Feature;
+import android.os.IRemoteCallback;
+import android.os.ICancellationSignal;
+import android.os.PersistableBundle;
+import android.os.Bundle;
+import com.android.internal.infra.AndroidFuture;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
+
+/**
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceSandboxedInferenceService {
+ void registerRemoteStorageService(in IRemoteStorageService storageService,
+ in IRemoteCallback remoteCallback) = 0;
+ void requestTokenInfo(int callerUid, in Feature feature, in Bundle request,
+ in AndroidFuture cancellationSignal,
+ in ITokenInfoCallback tokenInfoCallback) = 1;
+ void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
+ in AndroidFuture cancellationSignal,
+ in AndroidFuture processingSignal,
+ in IResponseCallback callback) = 2;
+ void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
+ in AndroidFuture cancellationSignal,
+ in AndroidFuture processingSignal,
+ in IStreamingResponseCallback callback) = 3;
+ void updateProcessingState(in Bundle processingState,
+ in IProcessingUpdateStatusCallback callback) = 4;
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
new file mode 100644
index 000000000000..7ead8690abb4
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
@@ -0,0 +1,14 @@
+package android.service.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving status from a updateProcessingState call from on-device intelligence
+ * service.
+ *
+ * @hide
+ */
+interface IProcessingUpdateStatusCallback {
+ void onSuccess(in PersistableBundle statusParams) = 1;
+ void onFailure(int errorCode, in String errorMessage) = 2;
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
new file mode 100644
index 000000000000..32a8a6a70406
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.os.Bundle;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
+
+
+/**
+ * Interface for a concrete implementation to provide methods to update state of a remote service.
+ *
+ * @hide
+ */
+oneway interface IRemoteProcessingService {
+ void updateProcessingState(in Bundle processingState,
+ in IProcessingUpdateStatusCallback callback);
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
new file mode 100644
index 000000000000..a6f49e17d80e
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * Interface for a concrete implementation to provide access to storage read access
+ * for the isolated process.
+ *
+ * @hide
+ */
+oneway interface IRemoteStorageService {
+ void getReadOnlyFileDescriptor(in String filePath, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
new file mode 100644
index 000000000000..618d2a096916
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+
+/**
+ * Abstract base class for performing setup for on-device inference and providing file access to
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
+ *
+ * <p> A service that provides configuration and model files relevant to performing inference on
+ * device. The system's default OnDeviceIntelligenceService implementation is configured in
+ * {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is
+ * returned.
+ *
+ * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are
+ * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended
+ * that implementations of this system-service expose this API to the clients via a library which
+ * has more defined contract.</p>
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleOnDeviceIntelligenceService"
+ * android:permission="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceIntelligenceService extends Service {
+ private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
+
+ private volatile IRemoteProcessingService mRemoteProcessingService;
+ private Handler mHandler;
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+ }
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceIntelligenceService.Stub() {
+ /** {@inheritDoc} */
+ @Override
+ public void ready() {
+ mHandler.executeOrSendMessage(
+ obtainMessage(OnDeviceIntelligenceService::onReady,
+ OnDeviceIntelligenceService.this));
+ }
+
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) {
+ Objects.requireNonNull(remoteCallback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onGetVersion,
+ OnDeviceIntelligenceService.this, l -> {
+ Bundle b = new Bundle();
+ b.putLong(
+ OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY,
+ l);
+ remoteCallback.sendResult(b);
+ }));
+ }
+
+ @Override
+ public void listFeatures(int callerUid,
+ IListFeaturesCallback listFeaturesCallback) {
+ Objects.requireNonNull(listFeaturesCallback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onListFeatures,
+ OnDeviceIntelligenceService.this, callerUid,
+ wrapListFeaturesCallback(listFeaturesCallback)));
+ }
+
+ @Override
+ public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
+ Objects.requireNonNull(featureCallback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onGetFeature,
+ OnDeviceIntelligenceService.this, callerUid,
+ id, wrapFeatureCallback(featureCallback)));
+ }
+
+
+ @Override
+ public void getFeatureDetails(int callerUid, Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onGetFeatureDetails,
+ OnDeviceIntelligenceService.this, callerUid,
+ feature, wrapFeatureDetailsCallback(featureDetailsCallback)));
+ }
+
+ @Override
+ public void requestFeatureDownload(int callerUid, Feature feature,
+ AndroidFuture cancellationSignalFuture,
+ IDownloadCallback downloadCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ ICancellationSignal transport = null;
+ if (cancellationSignalFuture != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignalFuture.complete(transport);
+ }
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onDownloadFeature,
+ OnDeviceIntelligenceService.this, callerUid,
+ feature,
+ CancellationSignal.fromTransport(transport),
+ wrapDownloadCallback(downloadCallback)));
+ }
+
+ @Override
+ public void getReadOnlyFileDescriptor(String fileName,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ Objects.requireNonNull(fileName);
+ Objects.requireNonNull(future);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor,
+ OnDeviceIntelligenceService.this, fileName,
+ future));
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature, RemoteCallback remoteCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(remoteCallback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap,
+ OnDeviceIntelligenceService.this, feature,
+ parcelFileDescriptorMap -> {
+ Bundle bundle = new Bundle();
+ parcelFileDescriptorMap.forEach(bundle::putParcelable);
+ remoteCallback.sendResult(bundle);
+ tryClosePfds(parcelFileDescriptorMap.values());
+ }));
+ }
+
+ @Override
+ public void registerRemoteServices(
+ IRemoteProcessingService remoteProcessingService) {
+ mRemoteProcessingService = remoteProcessingService;
+ }
+
+ @Override
+ public void notifyInferenceServiceConnected() {
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onInferenceServiceConnected,
+ OnDeviceIntelligenceService.this));
+ }
+
+ @Override
+ public void notifyInferenceServiceDisconnected() {
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceIntelligenceService::onInferenceServiceDisconnected,
+ OnDeviceIntelligenceService.this));
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ /**
+ * Using this signal to assertively a signal each time service binds successfully, used only in
+ * tests to get a signal that service instance is ready. This is needed because we cannot rely
+ * on {@link #onCreate} or {@link #onBind} to be invoke on each binding.
+ */
+ public void onReady() {
+ }
+
+
+ /**
+ * Invoked when a new instance of the remote inference service is created.
+ * This method should be used as a signal to perform any initialization operations, for e.g. by
+ * invoking the {@link #updateProcessingState} method to initialize the remote processing
+ * service.
+ */
+ public abstract void onInferenceServiceConnected();
+
+
+ /**
+ * Invoked when an instance of the remote inference service is disconnected.
+ */
+ public abstract void onInferenceServiceDisconnected();
+
+
+ /**
+ * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
+ * service if there is a state change to be performed. State change could be config updates,
+ * performing initialization or cleanup tasks in the remote inference service.
+ * The Bundle passed in here is expected to be read-only and will be rejected if it has any
+ * writable fields as detailed under {@link StateParams}.
+ *
+ * @param processingState the updated state to be applied.
+ * @param callbackExecutor executor to the run status callback on.
+ * @param statusReceiver receiver to get status of the update state operation.
+ */
+ public final void updateProcessingState(@NonNull @StateParams Bundle processingState,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
+ Objects.requireNonNull(callbackExecutor);
+ if (mRemoteProcessingService == null) {
+ throw new IllegalStateException("Remote processing service is unavailable.");
+ }
+ try {
+ mRemoteProcessingService.updateProcessingState(processingState,
+ new IProcessingUpdateStatusCallback.Stub() {
+ @Override
+ public void onSuccess(PersistableBundle result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> statusReceiver.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> statusReceiver.onError(
+ new OnDeviceIntelligenceException(
+ errorCode, errorMessage))));
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error in updateProcessingState: " + e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private OutcomeReceiver<Feature,
+ OnDeviceIntelligenceException> wrapFeatureCallback(
+ IFeatureCallback featureCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Feature feature) {
+ try {
+ featureCallback.onSuccess(feature);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceException exception) {
+ try {
+ featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
+ IListFeaturesCallback listFeaturesCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull List<Feature> features) {
+ try {
+ listFeaturesCallback.onSuccess(features);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceException exception) {
+ try {
+ listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
+ IFeatureDetailsCallback featureStatusCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(FeatureDetails result) {
+ try {
+ featureStatusCallback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceException exception) {
+ try {
+ featureStatusCallback.onFailure(exception.getErrorCode(),
+ exception.getMessage(), exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+ };
+ }
+
+
+ private DownloadCallback wrapDownloadCallback(IDownloadCallback downloadCallback) {
+ return new DownloadCallback() {
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ try {
+ downloadCallback.onDownloadStarted(bytesToDownload);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus,
+ String errorMessage, @NonNull PersistableBundle errorParams) {
+ try {
+ downloadCallback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadProgress(long totalBytesDownloaded) {
+ try {
+ downloadCallback.onDownloadProgress(totalBytesDownloaded);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadCompleted(@NonNull PersistableBundle persistableBundle) {
+ try {
+ downloadCallback.onDownloadCompleted(persistableBundle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+ };
+ }
+
+ private static void tryClosePfds(Collection<ParcelFileDescriptor> pfds) {
+ pfds.forEach(pfd -> {
+ try {
+ pfd.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Error closing FD", e);
+ }
+ });
+ }
+
+ private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
+ @NonNull AndroidFuture<ParcelFileDescriptor> future) {
+ Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
+ Binder.withCleanCallingIdentity(() -> {
+ Slog.v(TAG,
+ "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
+ File f = new File(getBaseContext().getFilesDir(), fileName);
+ if (!f.exists()) {
+ f = new File(fileName);
+ }
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
+ future.completeExceptionally(e);
+ } finally {
+ future.complete(pfd);
+ if (pfd != null) {
+ pfd.close();
+ }
+ }
+ });
+ }
+
+ /**
+ * Provide implementation for a scenario when caller wants to get all feature related
+ * file-descriptors that might be required for processing a request for the corresponding the
+ * feature.
+ *
+ * @param feature the feature for which files need to be opened.
+ * @param fileDescriptorMapConsumer callback to be populated with a map of file-path and
+ * corresponding ParcelDescriptor to be used in a remote
+ * service.
+ */
+ public abstract void onGetReadOnlyFeatureFileDescriptorMap(
+ @NonNull Feature feature,
+ @NonNull Consumer<Map<String, ParcelFileDescriptor>> fileDescriptorMapConsumer);
+
+ /**
+ * Request download for feature that is requested and listen to download progress updates. If
+ * the download completes successfully, success callback should be populated.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature the feature for which files need to be downlaoded.
+ * process.
+ * @param cancellationSignal signal to attach a listener to, and receive cancellation signals
+ * from thw client.
+ * @param downloadCallback callback to populate download updates for clients to listen on..
+ */
+ public abstract void onDownloadFeature(
+ int callerUid, @NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull DownloadCallback downloadCallback);
+
+ /**
+ * Provide feature details for the passed in feature. Usually the client and remote
+ * implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
+ * details the client is looking for.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature the feature for which status needs to be known.
+ * @param featureDetailsCallback callback to populate the resulting feature status.
+ */
+ public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
+ @NonNull OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceException> featureDetailsCallback);
+
+
+ /**
+ * Get feature using the provided identifier to the remote implementation.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param featureCallback callback to populate the features list.
+ */
+ public abstract void onGetFeature(int callerUid, int featureId,
+ @NonNull OutcomeReceiver<Feature,
+ OnDeviceIntelligenceException> featureCallback);
+
+ /**
+ * List all features which are available in the remote implementation. The implementation might
+ * choose to provide only a certain list of features based on the caller.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param listFeaturesCallback callback to populate the features list.
+ */
+ public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceException> listFeaturesCallback);
+
+ /**
+ * Provides a long value representing the version of the remote implementation processing
+ * requests.
+ *
+ * @param versionConsumer consumer to populate the version.
+ */
+ public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
+}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
new file mode 100644
index 000000000000..949fb8ddda9d
--- /dev/null
+++ b/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
+import android.app.ondeviceintelligence.ProcessingCallback;
+import android.app.ondeviceintelligence.ProcessingSignal;
+import android.app.ondeviceintelligence.StreamingProcessingCallback;
+import android.app.ondeviceintelligence.TokenInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for performing inference in a isolated process. This service exposes its
+ * methods via {@link OnDeviceIntelligenceManager}.
+ *
+ * <p> A service that provides methods to perform on-device inference both in streaming and
+ * non-streaming fashion. Also, provides a way to register a storage service that will be used to
+ * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p>
+ *
+ * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are
+ * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended
+ * that implementations of this system-service expose this API to the clients via a library which
+ * has more defined contract.</p>
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleSandboxedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
+ * android:isolatedProcess="true">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+ private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
+
+ /**
+ * @hide
+ */
+ public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
+
+ // TODO(339594686): make API
+ /**
+ * @hide
+ */
+ public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY =
+ "register_model_update_callback";
+ /**
+ * @hide
+ */
+ public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded";
+ /**
+ * @hide
+ */
+ public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded";
+ /**
+ * @hide
+ */
+ public static final String MODEL_LOADED_BROADCAST_INTENT =
+ "android.service.ondeviceintelligence.MODEL_LOADED";
+ /**
+ * @hide
+ */
+ public static final String MODEL_UNLOADED_BROADCAST_INTENT =
+ "android.service.ondeviceintelligence.MODEL_UNLOADED";
+
+ /**
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_UPDATE_BUNDLE_KEY = "device_config_update";
+
+ private IRemoteStorageService mRemoteStorageService;
+ private Handler mHandler;
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceSandboxedInferenceService.Stub() {
+ @Override
+ public void registerRemoteStorageService(IRemoteStorageService storageService,
+ IRemoteCallback remoteCallback) throws RemoteException {
+ Objects.requireNonNull(storageService);
+ mRemoteStorageService = storageService;
+ remoteCallback.sendResult(
+ Bundle.EMPTY); //to notify caller uid to system-server.
+ }
+
+ @Override
+ public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
+ AndroidFuture cancellationSignalFuture,
+ ITokenInfoCallback tokenInfoCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(tokenInfoCallback);
+ ICancellationSignal transport = null;
+ if (cancellationSignalFuture != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignalFuture.complete(transport);
+ }
+
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceSandboxedInferenceService::onTokenInfoRequest,
+ OnDeviceSandboxedInferenceService.this,
+ callerUid, feature,
+ request,
+ CancellationSignal.fromTransport(transport),
+ wrapTokenInfoCallback(tokenInfoCallback)));
+ }
+
+ @Override
+ public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
+ int requestType,
+ AndroidFuture cancellationSignalFuture,
+ AndroidFuture processingSignalFuture,
+ IStreamingResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(callback);
+
+ ICancellationSignal transport = null;
+ if (cancellationSignalFuture != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignalFuture.complete(transport);
+ }
+ IProcessingSignal processingSignalTransport = null;
+ if (processingSignalFuture != null) {
+ processingSignalTransport = ProcessingSignal.createTransport();
+ processingSignalFuture.complete(processingSignalTransport);
+ }
+
+
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceSandboxedInferenceService::onProcessRequestStreaming,
+ OnDeviceSandboxedInferenceService.this, callerUid,
+ feature,
+ request,
+ requestType,
+ CancellationSignal.fromTransport(transport),
+ ProcessingSignal.fromTransport(processingSignalTransport),
+ wrapStreamingResponseCallback(callback)));
+ }
+
+ @Override
+ public void processRequest(int callerUid, Feature feature, Bundle request,
+ int requestType,
+ AndroidFuture cancellationSignalFuture,
+ AndroidFuture processingSignalFuture,
+ IResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(callback);
+ ICancellationSignal transport = null;
+ if (cancellationSignalFuture != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignalFuture.complete(transport);
+ }
+ IProcessingSignal processingSignalTransport = null;
+ if (processingSignalFuture != null) {
+ processingSignalTransport = ProcessingSignal.createTransport();
+ processingSignalFuture.complete(processingSignalTransport);
+ }
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceSandboxedInferenceService::onProcessRequest,
+ OnDeviceSandboxedInferenceService.this, callerUid, feature,
+ request, requestType,
+ CancellationSignal.fromTransport(transport),
+ ProcessingSignal.fromTransport(processingSignalTransport),
+ wrapResponseCallback(callback)));
+ }
+
+ @Override
+ public void updateProcessingState(Bundle processingState,
+ IProcessingUpdateStatusCallback callback) {
+ Objects.requireNonNull(processingState);
+ Objects.requireNonNull(callback);
+ mHandler.executeOrSendMessage(
+ obtainMessage(
+ OnDeviceSandboxedInferenceService::onUpdateProcessingState,
+ OnDeviceSandboxedInferenceService.this, processingState,
+ wrapOutcomeReceiver(callback)));
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ /**
+ * Invoked when caller wants to obtain token info related to the payload in the passed
+ * content, associated with the provided feature.
+ * The expectation from the implementation is that when processing is complete, it
+ * should provide the token info in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param callback callback to populate failure or the token info for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onTokenInfoRequest(
+ int callerUid, @NonNull Feature feature,
+ @NonNull @InferenceParams Bundle request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in a
+ * streaming manner. The expectation from the implementation is that when processing the
+ * request,
+ * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to
+ * continuously
+ * provide partial Bundle results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon
+ * processing completion.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate the partial responses, failure and optionally
+ * full response for the provided request.
+ */
+ @NonNull
+ public abstract void onProcessRequestStreaming(
+ int callerUid, @NonNull Feature feature,
+ @NonNull @InferenceParams Bundle request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull StreamingProcessingCallback callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in one shot
+ * completely.
+ * The expectation from the implementation is that when processing the request is complete, it
+ * should
+ * provide the complete response in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate failure and full response for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onProcessRequest(
+ int callerUid, @NonNull Feature feature,
+ @NonNull @InferenceParams Bundle request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull ProcessingCallback callback);
+
+
+ /**
+ * Invoked when processing environment needs to be updated or refreshed with fresh
+ * configuration, files or state.
+ *
+ * @param processingState contains updated state and params that are to be applied to the
+ * processing environmment,
+ * @param callback callback to populate the update status and if there are params
+ * associated with the status.
+ */
+ public abstract void onUpdateProcessingState(@NonNull @StateParams Bundle processingState,
+ @NonNull OutcomeReceiver<PersistableBundle,
+ OnDeviceIntelligenceException> callback);
+
+
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given file names under the
+ * internal app storage of the {@link OnDeviceIntelligenceService}, i.e., only files stored in
+ * {@link Context#getFilesDir()} can be opened.
+ */
+ @Override
+ public final FileInputStream openFileInput(@NonNull String filename) throws
+ FileNotFoundException {
+ try {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mRemoteStorageService.getReadOnlyFileDescriptor(filename, future);
+ ParcelFileDescriptor pfd = future.get();
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | InterruptedException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+
+ /**
+ * Provides read-only access to the internal app storage via the
+ * {@link OnDeviceIntelligenceService}. This is an asynchronous alternative for
+ * {@link #openFileInput(String)}.
+ *
+ * @param fileName File name relative to the {@link Context#getFilesDir()}.
+ * @param resultConsumer Consumer to populate the corresponding file descriptor in.
+ */
+ public final void getReadOnlyFileDescriptor(@NonNull String fileName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ParcelFileDescriptor> resultConsumer) throws FileNotFoundException {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ try {
+ mRemoteStorageService.getReadOnlyFileDescriptor(fileName, future);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ future.whenCompleteAsync((pfd, err) -> {
+ if (err != null) {
+ Log.e(TAG, "Failure when reading file: " + fileName + err);
+ executor.execute(() -> resultConsumer.accept(null));
+ } else {
+ executor.execute(
+ () -> resultConsumer.accept(pfd));
+ }
+ }, executor);
+ }
+
+ /**
+ * Provides access to all file streams required for feature via the
+ * {@link OnDeviceIntelligenceService}.
+ *
+ * @param feature Feature for which the associated files should be fetched.
+ * @param executor Executor to run the consumer callback on.
+ * @param resultConsumer Consumer to receive a map of filePath to the corresponding file input
+ * stream.
+ */
+ public final void fetchFeatureFileDescriptorMap(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Map<String, ParcelFileDescriptor>> resultConsumer) {
+ try {
+ mRemoteStorageService.getReadOnlyFeatureFileDescriptorMap(feature,
+ wrapAsRemoteCallback(resultConsumer, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+ * implementation. For e.g. see
+ * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
+ * Consumer)} where we use the executor to populate the consumer.
+ * <p>
+ * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+ * provide the executor you want to use for incoming IPC.
+ *
+ * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+ * to {@link OnDeviceSandboxedInferenceService}.
+ */
+ @SuppressLint("OnNameExpected")
+ @NonNull
+ public Executor getCallbackExecutor() {
+ return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+ }
+
+
+ private RemoteCallback wrapAsRemoteCallback(
+ @NonNull Consumer<Map<String, ParcelFileDescriptor>> resultConsumer,
+ @NonNull Executor executor) {
+ return new RemoteCallback(result -> {
+ if (result == null) {
+ executor.execute(() -> resultConsumer.accept(new HashMap<>()));
+ } else {
+ Map<String, ParcelFileDescriptor> pfdMap = new HashMap<>();
+ result.keySet().forEach(key ->
+ pfdMap.put(key, result.getParcelable(key,
+ ParcelFileDescriptor.class)));
+ executor.execute(() -> resultConsumer.accept(pfdMap));
+ }
+ });
+ }
+
+ private ProcessingCallback wrapResponseCallback(
+ IResponseCallback callback) {
+ return new ProcessingCallback() {
+ @Override
+ public void onResult(@NonNull Bundle result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
+ };
+ }
+
+ private StreamingProcessingCallback wrapStreamingResponseCallback(
+ IStreamingResponseCallback callback) {
+ return new StreamingProcessingCallback() {
+ @Override
+ public void onPartialResult(@NonNull Bundle partialResult) {
+ try {
+ callback.onNewContent(partialResult);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onResult(@NonNull Bundle result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
+ };
+ }
+
+ private RemoteCallback wrapRemoteCallback(
+ @NonNull Consumer<Bundle> contentCallback) {
+ return new RemoteCallback(
+ result -> {
+ if (result != null) {
+ getCallbackExecutor().execute(() -> contentCallback.accept(
+ result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ Bundle.class)));
+ } else {
+ getCallbackExecutor().execute(
+ () -> contentCallback.accept(null));
+ }
+ });
+ }
+
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
+ ITokenInfoCallback tokenInfoCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(TokenInfo tokenInfo) {
+ try {
+ tokenInfoCallback.onSuccess(tokenInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceException exception) {
+ try {
+ tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending failure: " + e);
+ }
+ }
+ };
+ }
+
+ @NonNull
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
+ IProcessingUpdateStatusCallback callback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull PersistableBundle result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceException error) {
+ try {
+ callback.onFailure(error.getErrorCode(), error.getMessage());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending exception details: " + e);
+ }
+ }
+ };
+ }
+
+}
diff --git a/packages/NeuralNetworks/service/Android.bp b/packages/NeuralNetworks/service/Android.bp
index 05c603f5ebce..cfdc1af6857f 100644
--- a/packages/NeuralNetworks/service/Android.bp
+++ b/packages/NeuralNetworks/service/Android.bp
@@ -19,11 +19,20 @@ package {
filegroup {
name: "service-ondeviceintelligence-sources",
srcs: [
- "java/**/*.java",
+ "module/java/**/*.java",
],
- path: "java",
visibility: [
"//frameworks/base:__subpackages__",
"//packages/modules/NeuralNetworks:__subpackages__",
],
}
+
+filegroup {
+ name: "service-ondeviceintelligence-sources-platform",
+ srcs: [
+ "platform/java/**/*.java",
+ ],
+ visibility: [
+ "//frameworks/base:__subpackages__",
+ ],
+}
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 2626cc880e09..2626cc880e09 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/BundleUtil.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
index e8a1b322f661..e8a1b322f661 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
index 6badc53ae97e..6badc53ae97e 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index a078f7542c11..a078f7542c11 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index c641de8b47b1..c641de8b47b1 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index 0c43a309c456..0c43a309c456 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 8c5d5a7ba736..8c5d5a7ba736 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
index 249bcd37db5d..249bcd37db5d 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
+++ b/packages/NeuralNetworks/service/module/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/BundleUtil.java
new file mode 100644
index 000000000000..7dd8f2fdcecb
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -0,0 +1,407 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.system.OsConstants.F_GETFL;
+import static android.system.OsConstants.O_ACCMODE;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.PROT_READ;
+
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ResponseParams;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
+import android.app.ondeviceintelligence.TokenInfo;
+import android.database.CursorWindow;
+import android.graphics.Bitmap;
+import android.os.BadParcelableException;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
+ * some known types.
+ */
+public class BundleUtil {
+ private static final String TAG = "BundleUtil";
+
+ /**
+ * Validation of the inference request payload as described in {@link InferenceParams}
+ * description.
+ *
+ * @throws BadParcelableException when the bundle does not meet the read-only requirements.
+ */
+ public static void sanitizeInferenceParams(
+ @InferenceParams Bundle bundle) {
+ ensureValidBundle(bundle);
+
+ if (!bundle.hasFileDescriptors()) {
+ return; //safe to exit if there are no FDs and Binders
+ }
+
+ for (String key : bundle.keySet()) {
+ Object obj = bundle.get(key);
+ if (obj == null) {
+ /* Null value here could also mean deserializing a custom parcelable has failed,
+ * and since {@link Bundle} is marked as defusable in system-server - the
+ * {@link ClassNotFoundException} exception is swallowed and `null` is returned
+ * instead. We want to ensure cleanup of null entries in such case.
+ */
+ bundle.putObject(key, null);
+ continue;
+ }
+ if (canMarshall(obj) || obj instanceof CursorWindow) {
+ continue;
+ }
+ if (obj instanceof Bundle) {
+ sanitizeInferenceParams((Bundle) obj);
+ } else if (obj instanceof ParcelFileDescriptor) {
+ validatePfdReadOnly((ParcelFileDescriptor) obj);
+ } else if (obj instanceof SharedMemory) {
+ ((SharedMemory) obj).setProtect(PROT_READ);
+ } else if (obj instanceof Bitmap) {
+ validateBitmap((Bitmap) obj);
+ } else if (obj instanceof Parcelable[]) {
+ validateParcelableArray((Parcelable[]) obj);
+ } else {
+ throw new BadParcelableException(
+ "Unsupported Parcelable type encountered in the Bundle: "
+ + obj.getClass().getSimpleName());
+ }
+ }
+ }
+
+ /**
+ * Validation of the inference request payload as described in {@link ResponseParams}
+ * description.
+ *
+ * @throws BadParcelableException when the bundle does not meet the read-only requirements.
+ */
+ public static void sanitizeResponseParams(
+ @ResponseParams Bundle bundle) {
+ ensureValidBundle(bundle);
+
+ if (!bundle.hasFileDescriptors()) {
+ return; //safe to exit if there are no FDs and Binders
+ }
+
+ for (String key : bundle.keySet()) {
+ Object obj = bundle.get(key);
+ if (obj == null) {
+ /* Null value here could also mean deserializing a custom parcelable has failed,
+ * and since {@link Bundle} is marked as defusable in system-server - the
+ * {@link ClassNotFoundException} exception is swallowed and `null` is returned
+ * instead. We want to ensure cleanup of null entries in such case.
+ */
+ bundle.putObject(key, null);
+ continue;
+ }
+ if (canMarshall(obj)) {
+ continue;
+ }
+
+ if (obj instanceof Bundle) {
+ sanitizeResponseParams((Bundle) obj);
+ } else if (obj instanceof ParcelFileDescriptor) {
+ validatePfdReadOnly((ParcelFileDescriptor) obj);
+ } else if (obj instanceof Bitmap) {
+ validateBitmap((Bitmap) obj);
+ } else if (obj instanceof Parcelable[]) {
+ validateParcelableArray((Parcelable[]) obj);
+ } else {
+ throw new BadParcelableException(
+ "Unsupported Parcelable type encountered in the Bundle: "
+ + obj.getClass().getSimpleName());
+ }
+ }
+ }
+
+ /**
+ * Validation of the inference request payload as described in {@link StateParams}
+ * description.
+ *
+ * @throws BadParcelableException when the bundle does not meet the read-only requirements.
+ */
+ public static void sanitizeStateParams(
+ @StateParams Bundle bundle) {
+ ensureValidBundle(bundle);
+
+ if (!bundle.hasFileDescriptors()) {
+ return; //safe to exit if there are no FDs and Binders
+ }
+
+ for (String key : bundle.keySet()) {
+ Object obj = bundle.get(key);
+ if (obj == null) {
+ /* Null value here could also mean deserializing a custom parcelable has failed,
+ * and since {@link Bundle} is marked as defusable in system-server - the
+ * {@link ClassNotFoundException} exception is swallowed and `null` is returned
+ * instead. We want to ensure cleanup of null entries in such case.
+ */
+ bundle.putObject(key, null);
+ continue;
+ }
+ if (canMarshall(obj)) {
+ continue;
+ }
+
+ if (obj instanceof ParcelFileDescriptor) {
+ validatePfdReadOnly((ParcelFileDescriptor) obj);
+ } else {
+ throw new BadParcelableException(
+ "Unsupported Parcelable type encountered in the Bundle: "
+ + obj.getClass().getSimpleName());
+ }
+ }
+ }
+
+
+ public static IStreamingResponseCallback wrapWithValidation(
+ IStreamingResponseCallback streamingResponseCallback,
+ Executor resourceClosingExecutor,
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
+ return new IStreamingResponseCallback.Stub() {
+ @Override
+ public void onNewContent(Bundle processedResult) throws RemoteException {
+ try {
+ sanitizeResponseParams(processedResult);
+ streamingResponseCallback.onNewContent(processedResult);
+ } finally {
+ resourceClosingExecutor.execute(() -> tryCloseResource(processedResult));
+ }
+ }
+
+ @Override
+ public void onSuccess(Bundle resultBundle)
+ throws RemoteException {
+ try {
+ sanitizeResponseParams(resultBundle);
+ streamingResponseCallback.onSuccess(resultBundle);
+ } finally {
+ inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
+ resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+ future.complete(null);
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) throws RemoteException {
+ streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+
+ @Override
+ public void onDataAugmentRequest(Bundle processedContent,
+ RemoteCallback remoteCallback)
+ throws RemoteException {
+ try {
+ sanitizeResponseParams(processedContent);
+ streamingResponseCallback.onDataAugmentRequest(processedContent,
+ new RemoteCallback(
+ augmentedData -> {
+ try {
+ sanitizeInferenceParams(augmentedData);
+ remoteCallback.sendResult(augmentedData);
+ } finally {
+ resourceClosingExecutor.execute(
+ () -> tryCloseResource(augmentedData));
+ }
+ }));
+ } finally {
+ resourceClosingExecutor.execute(() -> tryCloseResource(processedContent));
+ }
+ }
+ };
+ }
+
+ public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
+ Executor resourceClosingExecutor,
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
+ return new IResponseCallback.Stub() {
+ @Override
+ public void onSuccess(Bundle resultBundle)
+ throws RemoteException {
+ try {
+ sanitizeResponseParams(resultBundle);
+ responseCallback.onSuccess(resultBundle);
+ } finally {
+ inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
+ resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+ future.complete(null);
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) throws RemoteException {
+ responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+
+ @Override
+ public void onDataAugmentRequest(Bundle processedContent,
+ RemoteCallback remoteCallback)
+ throws RemoteException {
+ try {
+ sanitizeResponseParams(processedContent);
+ responseCallback.onDataAugmentRequest(processedContent, new RemoteCallback(
+ augmentedData -> {
+ try {
+ sanitizeInferenceParams(augmentedData);
+ remoteCallback.sendResult(augmentedData);
+ } finally {
+ resourceClosingExecutor.execute(
+ () -> tryCloseResource(augmentedData));
+ }
+ }));
+ } finally {
+ resourceClosingExecutor.execute(() -> tryCloseResource(processedContent));
+ }
+ }
+ };
+ }
+
+
+ public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
+ AndroidFuture future,
+ InferenceInfoStore inferenceInfoStore) {
+ return new ITokenInfoCallback.Stub() {
+ @Override
+ public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
+ responseCallback.onSuccess(tokenInfo);
+ inferenceInfoStore.addInferenceInfoFromBundle(tokenInfo.getInfoParams());
+ future.complete(null);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
+ throws RemoteException {
+ responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+ };
+ }
+
+ private static boolean canMarshall(Object obj) {
+ return obj instanceof byte[] || obj instanceof PersistableBundle
+ || PersistableBundle.isValidType(obj);
+ }
+
+ private static void ensureValidBundle(Bundle bundle) {
+ if (bundle == null) {
+ throw new IllegalArgumentException("Request passed is expected to be non-null");
+ }
+
+ if (bundle.hasBinders() != Bundle.STATUS_BINDERS_NOT_PRESENT) {
+ throw new BadParcelableException("Bundle should not contain IBinder objects.");
+ }
+ }
+
+ private static void validateParcelableArray(Parcelable[] parcelables) {
+ if (parcelables.length > 0
+ && parcelables[0] instanceof ParcelFileDescriptor) {
+ // Safe to cast
+ validatePfdsReadOnly(parcelables);
+ } else if (parcelables.length > 0
+ && parcelables[0] instanceof Bitmap) {
+ validateBitmapsImmutable(parcelables);
+ } else {
+ throw new BadParcelableException(
+ "Could not cast to any known parcelable array");
+ }
+ }
+
+ public static void validatePfdsReadOnly(Parcelable[] pfds) {
+ for (Parcelable pfd : pfds) {
+ validatePfdReadOnly((ParcelFileDescriptor) pfd);
+ }
+ }
+
+ public static void validatePfdReadOnly(ParcelFileDescriptor pfd) {
+ if (pfd == null) {
+ return;
+ }
+ try {
+ int readMode = Os.fcntlInt(pfd.getFileDescriptor(), F_GETFL, 0) & O_ACCMODE;
+ if (readMode != O_RDONLY) {
+ throw new BadParcelableException(
+ "Bundle contains a parcel file descriptor which is not read-only.");
+ }
+ } catch (ErrnoException e) {
+ throw new BadParcelableException(
+ "Invalid File descriptor passed in the Bundle.", e);
+ }
+ }
+
+ private static void validateBitmap(Bitmap obj) {
+ if (obj.isMutable()) {
+ throw new BadParcelableException(
+ "Encountered a mutable Bitmap in the Bundle at key : " + obj);
+ }
+ }
+
+ private static void validateBitmapsImmutable(Parcelable[] bitmaps) {
+ for (Parcelable bitmap : bitmaps) {
+ validateBitmap((Bitmap) bitmap);
+ }
+ }
+
+ public static void tryCloseResource(Bundle bundle) {
+ if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) {
+ return;
+ }
+
+ for (String key : bundle.keySet()) {
+ Object obj = bundle.get(key);
+
+ try {
+ // TODO(b/329898589) : This can be cleaned up after the flag passing is fixed.
+ if (obj instanceof ParcelFileDescriptor) {
+ ((ParcelFileDescriptor) obj).close();
+ } else if (obj instanceof CursorWindow) {
+ ((CursorWindow) obj).close();
+ } else if (obj instanceof SharedMemory) {
+ // TODO(b/331796886) : Shared memory should honour parcelable flags.
+ ((SharedMemory) obj).close();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error closing resource with key: " + key, e);
+ }
+ }
+ }
+}
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
new file mode 100644
index 000000000000..bef3f8048da1
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.InferenceInfo;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
+import android.util.Base64;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeSet;
+
+public class InferenceInfoStore {
+ private static final String TAG = "InferenceInfoStore";
+ private final TreeSet<InferenceInfo> inferenceInfos;
+ private final long maxAgeMs;
+
+ public InferenceInfoStore(long maxAgeMs) {
+ this.maxAgeMs = maxAgeMs;
+ this.inferenceInfos = new TreeSet<>(
+ Comparator.comparingLong(InferenceInfo::getStartTimeMillis));
+ }
+
+ public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ return inferenceInfos.stream().filter(
+ info -> info.getStartTimeMillis() > startTimeEpochMillis).toList();
+ }
+
+ public void addInferenceInfoFromBundle(PersistableBundle pb) {
+ if (!pb.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+ return;
+ }
+
+ try {
+ String infoBytesBase64String = pb.getString(
+ OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+ if (infoBytesBase64String != null) {
+ byte[] infoBytes = Base64.decode(infoBytesBase64String, Base64.DEFAULT);
+ com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+ com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+ infoBytes);
+ add(inferenceInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+ }
+ }
+
+ public void addInferenceInfoFromBundle(Bundle b) {
+ if (!b.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+ return;
+ }
+
+ try {
+ byte[] infoBytes = b.getByteArray(
+ OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+ if (infoBytes != null) {
+ com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+ com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+ infoBytes);
+ add(inferenceInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+ }
+ }
+
+ private synchronized void add(com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+ while (!inferenceInfos.isEmpty()
+ && System.currentTimeMillis() - inferenceInfos.first().getStartTimeMillis()
+ > maxAgeMs) {
+ inferenceInfos.pollFirst();
+ }
+ inferenceInfos.add(toInferenceInfo(info));
+ }
+
+ private static InferenceInfo toInferenceInfo(
+ com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+ return new InferenceInfo.Builder(info.uid).setStartTimeMillis(
+ info.startTimeMs).setEndTimeMillis(info.endTimeMs).setSuspendedTimeMillis(
+ info.suspendedTimeMs).build();
+ }
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
new file mode 100644
index 000000000000..6badc53ae97e
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+
+/**
+ * Exposes APIs to {@code system_server} components outside of the module boundaries.
+ * <p> This API should be access using {@link com.android.server.LocalManagerRegistry}. </p>
+ *
+ * @hide
+ */
+@SystemApi(client = Client.SYSTEM_SERVER)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
+public interface OnDeviceIntelligenceManagerLocal {
+ /**
+ * Gets the uid for the process that is currently hosting the
+ * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
+ * the device.
+ */
+ int getInferenceServiceUid();
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
new file mode 100644
index 000000000000..0a69af67a8bf
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -0,0 +1,1096 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
+
+import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
+import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
+import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
+import static com.android.server.ondeviceintelligence.BundleUtil.wrapWithValidation;
+
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.AppGlobals;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.InferenceInfo;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
+import android.service.ondeviceintelligence.IRemoteProcessingService;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
+import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This is the system service for handling calls on the
+ * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
+ * service holds connection references to the underlying remote services i.e. the isolated service
+ * {@link OnDeviceSandboxedInferenceService} and a regular
+ * service counter part {@link OnDeviceIntelligenceService}.
+ *
+ * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
+ * the Inference service for each user, due to possible high memory footprint.
+ *
+ * @hide
+ */
+public class OnDeviceIntelligenceManagerService extends SystemService {
+
+ private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Handler message to {@link #resetTemporaryServices()} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+ /** Handler message to clean up temporary broadcast keys. */
+ private static final int MSG_RESET_BROADCAST_KEYS = 1;
+ /** Handler message to clean up temporary config namespace. */
+ private static final int MSG_RESET_CONFIG_NAMESPACE = 2;
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+ private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
+
+ private static final String SYSTEM_PACKAGE = "android";
+ private static final long MAX_AGE_MS = TimeUnit.HOURS.toMillis(3);
+
+
+ private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
+ private final Executor callbackExecutor = Executors.newCachedThreadPool();
+ private final Executor broadcastExecutor = Executors.newCachedThreadPool();
+ private final Executor mConfigExecutor = Executors.newCachedThreadPool();
+
+
+ private final Context mContext;
+ protected final Object mLock = new Object();
+
+ private final InferenceInfoStore mInferenceInfoStore;
+ private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
+ volatile boolean mIsServiceEnabled;
+
+ @GuardedBy("mLock")
+ private int remoteInferenceServiceUid = -1;
+
+ @GuardedBy("mLock")
+ private String[] mTemporaryServiceNames;
+ @GuardedBy("mLock")
+ private String[] mTemporaryBroadcastKeys;
+ @GuardedBy("mLock")
+ private String mBroadcastPackageName = SYSTEM_PACKAGE;
+ @GuardedBy("mLock")
+ private String mTemporaryConfigNamespace;
+
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ this::sendUpdatedConfig;
+
+
+ /**
+ * Handler used to reset the temporary service names.
+ */
+ private Handler mTemporaryHandler;
+ private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+
+ public OnDeviceIntelligenceManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mTemporaryServiceNames = new String[0];
+ mInferenceInfoStore = new InferenceInfoStore(MAX_AGE_MS);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
+ /* allowIsolated = */ true);
+ LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class,
+ this::getRemoteInferenceServiceUid);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ BackgroundThread.getExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ if (keys.contains(KEY_SERVICE_ENABLED)) {
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private boolean isServiceEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ }
+
+ private IBinder getOnDeviceIntelligenceManagerService() {
+ return new IOnDeviceIntelligenceManager.Stub() {
+ @Override
+ public String getRemoteServicePackageName() {
+ return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
+ }
+
+ @Override
+ public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.DUMP, TAG);
+ return OnDeviceIntelligenceManagerService.this.getLatestInferenceInfo(
+ startTimeEpochMillis);
+ }
+
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getVersion(new RemoteCallback(
+ result -> {
+ remoteCallback.sendResult(result);
+ future.complete(null);
+ }));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ }
+
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getFeature(callerUid, id, new IFeatureCallback.Stub() {
+ @Override
+ public void onSuccess(Feature result) throws RemoteException {
+ featureCallback.onSuccess(result);
+ future.complete(null);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) throws RemoteException {
+ featureCallback.onFailure(errorCode, errorMessage, errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ }
+
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.listFeatures(callerUid,
+ new IListFeaturesCallback.Stub() {
+ @Override
+ public void onSuccess(List<Feature> result)
+ throws RemoteException {
+ listFeaturesCallback.onSuccess(result);
+ future.complete(null);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams)
+ throws RemoteException {
+ listFeaturesCallback.onFailure(errorCode, errorMessage,
+ errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ }
+
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getFeatureDetails(callerUid, feature,
+ new IFeatureDetailsCallback.Stub() {
+ @Override
+ public void onSuccess(FeatureDetails result)
+ throws RemoteException {
+ future.complete(null);
+ featureDetailsCallback.onSuccess(result);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams)
+ throws RemoteException {
+ future.completeExceptionally(null);
+ featureDetailsCallback.onFailure(errorCode,
+ errorMessage, errorParams);
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ AndroidFuture cancellationSignalFuture,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ ListenableDownloadCallback listenableDownloadCallback =
+ new ListenableDownloadCallback(
+ downloadCallback,
+ mMainHandler, future, getIdleTimeoutMs());
+ service.requestFeatureDownload(callerUid, feature,
+ wrapCancellationFuture(cancellationSignalFuture),
+ listenableDownloadCallback);
+ return future; // this future has no timeout because, actual download
+ // might take long, but we fail early if there is no progress callbacks.
+ }
+ );
+ }
+
+
+ @Override
+ public void requestTokenInfo(Feature feature,
+ Bundle request,
+ AndroidFuture cancellationSignalFuture,
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestTokenInfo");
+ AndroidFuture<Void> result = null;
+ try {
+ Objects.requireNonNull(feature);
+ sanitizeInferenceParams(request);
+ Objects.requireNonNull(tokenInfoCallback);
+
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenInfoCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.requestTokenInfo(callerUid, feature,
+ request,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapWithValidation(tokenInfoCallback, future,
+ mInferenceInfoStore));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
+ resourceClosingExecutor);
+ } finally {
+ if (result == null) {
+ resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
+ }
+ }
+ }
+
+ @Override
+ public void processRequest(Feature feature,
+ Bundle request,
+ int requestType,
+ AndroidFuture cancellationSignalFuture,
+ AndroidFuture processingSignalFuture,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ AndroidFuture<Void> result = null;
+ try {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ sanitizeInferenceParams(request);
+ Objects.requireNonNull(responseCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.processRequest(callerUid, feature,
+ request,
+ requestType,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapProcessingFuture(processingSignalFuture),
+ wrapWithValidation(responseCallback,
+ resourceClosingExecutor, future,
+ mInferenceInfoStore));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
+ resourceClosingExecutor);
+ } finally {
+ if (result == null) {
+ resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
+ }
+ }
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Bundle request,
+ int requestType,
+ AndroidFuture cancellationSignalFuture,
+ AndroidFuture processingSignalFuture,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ AndroidFuture<Void> result = null;
+ try {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ sanitizeInferenceParams(request);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ int callerUid = Binder.getCallingUid();
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.processRequestStreaming(callerUid,
+ feature,
+ request, requestType,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapProcessingFuture(processingSignalFuture),
+ wrapWithValidation(streamingCallback,
+ resourceClosingExecutor, future,
+ mInferenceInfoStore));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
+ result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
+ resourceClosingExecutor);
+ } finally {
+ if (result == null) {
+ resourceClosingExecutor.execute(() -> BundleUtil.tryCloseResource(request));
+ }
+ }
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+ };
+ }
+
+ private void ensureRemoteIntelligenceServiceInitialized() {
+ synchronized (mLock) {
+ if (mRemoteOnDeviceIntelligenceService == null) {
+ String serviceName = getServiceNames()[0];
+ Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false));
+ mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onConnected(
+ @NonNull IOnDeviceIntelligenceService service) {
+ try {
+ service.registerRemoteServices(
+ getRemoteProcessingService());
+ service.ready();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send connected event", ex);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @NonNull
+ private IRemoteProcessingService.Stub getRemoteProcessingService() {
+ return new IRemoteProcessingService.Stub() {
+ @Override
+ public void updateProcessingState(
+ Bundle processingState,
+ IProcessingUpdateStatusCallback callback) {
+ callbackExecutor.execute(() -> {
+ AndroidFuture<Void> result = null;
+ try {
+ sanitizeStateParams(processingState);
+ ensureRemoteInferenceServiceInitialized();
+ result = mRemoteInferenceService.post(
+ service -> service.updateProcessingState(
+ processingState, callback));
+ result.whenCompleteAsync(
+ (c, e) -> BundleUtil.tryCloseResource(processingState),
+ resourceClosingExecutor);
+ } finally {
+ if (result == null) {
+ resourceClosingExecutor.execute(
+ () -> BundleUtil.tryCloseResource(processingState));
+ }
+ }
+ });
+ }
+ };
+ }
+
+ private void ensureRemoteInferenceServiceInitialized() {
+ synchronized (mLock) {
+ if (mRemoteInferenceService == null) {
+ String serviceName = getServiceNames()[1];
+ Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true));
+ mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ mRemoteInferenceService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onConnected(
+ @NonNull IOnDeviceSandboxedInferenceService service) {
+ try {
+ ensureRemoteIntelligenceServiceInitialized();
+ service.registerRemoteStorageService(
+ getIRemoteStorageService(), new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle bundle) {
+ final int uid = Binder.getCallingUid();
+ setRemoteInferenceServiceUid(uid);
+ }
+ });
+ mRemoteOnDeviceIntelligenceService.run(
+ IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
+ broadcastExecutor.execute(
+ () -> registerModelLoadingBroadcasts(service));
+ mConfigExecutor.execute(
+ () -> registerDeviceConfigChangeListener());
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send connected event", ex);
+ }
+ }
+
+ @Override
+ public void onDisconnected(
+ @NonNull IOnDeviceSandboxedInferenceService service) {
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
+ }
+
+ @Override
+ public void onBinderDied() {
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
+ }
+ });
+ }
+ }
+ }
+
+ private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
+ String[] modelBroadcastKeys;
+ try {
+ modelBroadcastKeys = getBroadcastKeys();
+ } catch (Resources.NotFoundException e) {
+ Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured.");
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true);
+ try {
+ service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() {
+ @Override
+ public void onSuccess(PersistableBundle statusParams) {
+ Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) {
+ String modelLoadedBroadcastKey = modelBroadcastKeys[0];
+ if (modelLoadedBroadcastKey != null
+ && !modelLoadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelLoadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) {
+ String modelUnloadedBroadcastKey = modelBroadcastKeys[1];
+ if (modelUnloadedBroadcastKey != null
+ && !modelUnloadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelUnloadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage) {
+ Slog.e(TAG, "Failed to register model loading callback with status code",
+ new OnDeviceIntelligenceException(errorCode, errorMessage));
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register model loading callback with status code", e);
+ }
+ }
+
+ private void registerDeviceConfigChangeListener() {
+ Log.d(TAG, "registerDeviceConfigChangeListener");
+ String configNamespace = getConfigNamespace();
+ if (configNamespace.isEmpty()) {
+ Slog.e(TAG, "config_defaultOnDeviceIntelligenceDeviceConfigNamespace is empty");
+ return;
+ }
+ DeviceConfig.addOnPropertiesChangedListener(
+ configNamespace,
+ mConfigExecutor,
+ mOnPropertiesChangedListener);
+ }
+
+ private String getConfigNamespace() {
+ synchronized (mLock) {
+ if (mTemporaryConfigNamespace != null) {
+ return mTemporaryConfigNamespace;
+ }
+
+ return mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace);
+ }
+ }
+
+ private void sendUpdatedConfig(
+ DeviceConfig.Properties props) {
+ Log.d(TAG, "sendUpdatedConfig");
+
+ PersistableBundle persistableBundle = new PersistableBundle();
+ for (String key : props.getKeyset()) {
+ persistableBundle.putString(key, props.getString(key, ""));
+ }
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle);
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(service -> service.updateProcessingState(bundle,
+ new IProcessingUpdateStatusCallback.Stub() {
+ @Override
+ public void onSuccess(PersistableBundle result) {
+ Slog.d(TAG, "Config update successful." + result);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage) {
+ Slog.e(TAG, "Config update failed with code ["
+ + String.valueOf(errorCode) + "] and message = " + errorMessage);
+ }
+ }));
+ }
+
+ @NonNull
+ private IRemoteStorageService.Stub getIRemoteStorageService() {
+ return new IRemoteStorageService.Stub() {
+ @Override
+ public void getReadOnlyFileDescriptor(
+ String filePath,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ ensureRemoteIntelligenceServiceInitialized();
+ AndroidFuture<ParcelFileDescriptor> pfdFuture = new AndroidFuture<>();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getReadOnlyFileDescriptor(
+ filePath, pfdFuture));
+ pfdFuture.whenCompleteAsync((pfd, error) -> {
+ try {
+ if (error != null) {
+ future.completeExceptionally(error);
+ } else {
+ validatePfdReadOnly(pfd);
+ future.complete(pfd);
+ }
+ } finally {
+ tryClosePfd(pfd);
+ }
+ }, callbackExecutor);
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature,
+ RemoteCallback remoteCallback) {
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getReadOnlyFeatureFileDescriptorMap(
+ feature,
+ new RemoteCallback(result -> callbackExecutor.execute(() -> {
+ try {
+ if (result == null) {
+ remoteCallback.sendResult(null);
+ }
+ for (String key : result.keySet()) {
+ ParcelFileDescriptor pfd = result.getParcelable(key,
+ ParcelFileDescriptor.class);
+ validatePfdReadOnly(pfd);
+ }
+ remoteCallback.sendResult(result);
+ } finally {
+ resourceClosingExecutor.execute(
+ () -> BundleUtil.tryCloseResource(result));
+ }
+ }))));
+ }
+ };
+ }
+
+ private void validateServiceElevated(String serviceName, boolean checkIsolated) {
+ try {
+ if (TextUtils.isEmpty(serviceName)) {
+ throw new IllegalStateException(
+ "Remote service is not configured to complete the request");
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceName);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.SYSTEM.getIdentifier());
+ if (serviceInfo != null) {
+ if (!checkIsolated) {
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
+ return;
+ }
+
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
+ if (!isIsolatedService(serviceInfo)) {
+ throw new SecurityException(
+ "Call required an isolated service, but the configured service: "
+ + serviceName + ", is not isolated");
+ }
+ } else {
+ throw new IllegalStateException(
+ "Remote service is not configured to complete the request.");
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Could not fetch service info for remote services", e);
+ }
+ }
+
+ private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
+ String requiredPermission) {
+ final String permission = serviceInfo.permission;
+ if (!requiredPermission.equals(permission)) {
+ throw new SecurityException(String.format(
+ "Service %s requires %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ requiredPermission,
+ serviceInfo.permission));
+ }
+ }
+
+ private static boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
+ return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+ }
+
+ private List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+ return mInferenceInfoStore.getLatestInferenceInfo(startTimeEpochMillis);
+ }
+
+ @Nullable
+ public String getRemoteConfiguredPackageName() {
+ try {
+ String[] serviceNames = getServiceNames();
+ ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Could not find resource", e);
+ }
+
+ return null;
+ }
+
+
+ protected String[] getServiceNames() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
+ return mTemporaryServiceNames;
+ }
+ }
+ return new String[]{mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService),
+ mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService)};
+ }
+
+ protected String[] getBroadcastKeys() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) {
+ return mTemporaryBroadcastKeys;
+ }
+ }
+
+ return new String[]{ MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT };
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
+ Objects.requireNonNull(componentNames);
+ enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryServiceNames = componentNames;
+ if (mRemoteInferenceService != null) {
+ mRemoteInferenceService.unbind();
+ mRemoteInferenceService = null;
+ }
+ if (mRemoteOnDeviceIntelligenceService != null) {
+ mRemoteOnDeviceIntelligenceService.unbind();
+ mRemoteOnDeviceIntelligenceService = null;
+ }
+
+ if (durationMs != -1) {
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE,
+ durationMs);
+ }
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName,
+ int durationMs) {
+ Objects.requireNonNull(broadcastKeys);
+ enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryBroadcastKeys = broadcastKeys;
+ mBroadcastPackageName = receiverPackageName;
+ if (durationMs != -1) {
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs);
+ }
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setTemporaryDeviceConfigNamespace(@NonNull String configNamespace,
+ int durationMs) {
+ Objects.requireNonNull(configNamespace);
+ enforceShellOnly(Binder.getCallingUid(), "setTemporaryDeviceConfigNamespace");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryConfigNamespace = configNamespace;
+ if (durationMs != -1) {
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_CONFIG_NAMESPACE,
+ durationMs);
+ }
+ }
+ }
+
+ /**
+ * Reset the temporary services set in CTS tests, this method is primarily used to only revert
+ * the changes caused by CTS tests.
+ */
+ public void resetTemporaryServices() {
+ synchronized (mLock) {
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+
+ mRemoteInferenceService = null;
+ mRemoteOnDeviceIntelligenceService = null;
+ mTemporaryServiceNames = new String[0];
+ }
+ }
+
+ /**
+ * Throws if the caller is not of a shell (or root) UID.
+ *
+ * @param callingUid pass Binder.callingUid().
+ */
+ public static void enforceShellOnly(int callingUid, String message) {
+ if (callingUid == android.os.Process.SHELL_UID
+ || callingUid == android.os.Process.ROOT_UID) {
+ return; // okay
+ }
+
+ throw new SecurityException(message + ": Only shell user can call it");
+ }
+
+ private AndroidFuture<IBinder> wrapCancellationFuture(
+ AndroidFuture future) {
+ if (future == null) {
+ return null;
+ }
+ AndroidFuture<IBinder> cancellationFuture = new AndroidFuture<>();
+ cancellationFuture.whenCompleteAsync((c, e) -> {
+ if (e != null) {
+ Log.e(TAG, "Error forwarding ICancellationSignal to manager layer", e);
+ future.completeExceptionally(e);
+ } else {
+ future.complete(new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ ICancellationSignal.Stub.asInterface(c).cancel();
+ }
+ });
+ }
+ });
+ return cancellationFuture;
+ }
+
+ private AndroidFuture<IBinder> wrapProcessingFuture(
+ AndroidFuture future) {
+ if (future == null) {
+ return null;
+ }
+ AndroidFuture<IBinder> processingSignalFuture = new AndroidFuture<>();
+ processingSignalFuture.whenCompleteAsync((c, e) -> {
+ if (e != null) {
+ future.completeExceptionally(e);
+ } else {
+ future.complete(new IProcessingSignal.Stub() {
+ @Override
+ public void sendSignal(PersistableBundle actionParams) throws RemoteException {
+ IProcessingSignal.Stub.asInterface(c).sendSignal(actionParams);
+ }
+ });
+ }
+ });
+ return processingSignalFuture;
+ }
+
+ private static void tryClosePfd(ParcelFileDescriptor pfd) {
+ if (pfd != null) {
+ try {
+ pfd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close parcel file descriptor ", e);
+ }
+ }
+ }
+
+ private synchronized Handler getTemporaryHandler() {
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ resetTemporaryServices();
+ } else if (msg.what == MSG_RESET_BROADCAST_KEYS) {
+ mTemporaryBroadcastKeys = null;
+ mBroadcastPackageName = SYSTEM_PACKAGE;
+ } else if (msg.what == MSG_RESET_CONFIG_NAMESPACE) {
+ mTemporaryConfigNamespace = null;
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ }
+ };
+ }
+
+ return mTemporaryHandler;
+ }
+
+ private long getIdleTimeoutMs() {
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
+ mContext.getUserId());
+ }
+
+ private int getRemoteInferenceServiceUid() {
+ synchronized (mLock) {
+ return remoteInferenceServiceUid;
+ }
+ }
+
+ private void setRemoteInferenceServiceUid(int remoteInferenceServiceUid) {
+ synchronized (mLock) {
+ this.remoteInferenceServiceUid = remoteInferenceServiceUid;
+ }
+ }
+}
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 000000000000..d2c84fa1b18a
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+ private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
+
+ @NonNull
+ private final OnDeviceIntelligenceManagerService mService;
+
+ OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "set-temporary-services":
+ return setTemporaryServices();
+ case "get-services":
+ return getConfiguredServices();
+ case "set-model-broadcasts":
+ return setBroadcastKeys();
+ case "set-deviceconfig-namespace":
+ return setDeviceConfigNamespace();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("OnDeviceIntelligenceShellCommand commands: ");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(
+ " set-temporary-services [IntelligenceServiceComponentName] "
+ + "[InferenceServiceComponentName] [DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implementations.");
+ pw.println(" To reset, call without any arguments.");
+
+ pw.println(" get-services To get the names of services that are currently being used.");
+ pw.println(
+ " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] "
+ + "[ReceiverPackageName] "
+ + "[DURATION] To set the names of broadcast intent keys that are to be "
+ + "emitted for cts tests.");
+ pw.println(
+ " set-deviceconfig-namespace [DeviceConfigNamespace] "
+ + "[DURATION] To set the device config namespace "
+ + "to use for cts tests.");
+ }
+
+ private int setTemporaryServices() {
+ final PrintWriter out = getOutPrintWriter();
+ final String intelligenceServiceName = getNextArg();
+ final String inferenceServiceName = getNextArg();
+
+ if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+ && inferenceServiceName == null) {
+ OnDeviceIntelligenceManagerService.enforceShellOnly(Binder.getCallingUid(),
+ "resetTemporaryServices");
+ mService.resetTemporaryServices();
+ out.println("OnDeviceIntelligenceManagerService temporary reset. ");
+ return 0;
+ }
+
+ Objects.requireNonNull(intelligenceServiceName);
+ Objects.requireNonNull(inferenceServiceName);
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryServices(
+ new String[]{intelligenceServiceName, inferenceServiceName},
+ duration);
+ out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int getConfiguredServices() {
+ final PrintWriter out = getOutPrintWriter();
+ String[] services = mService.getServiceNames();
+ out.println("OnDeviceIntelligenceService set to : " + services[0]
+ + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
+ return 0;
+ }
+
+ private int setBroadcastKeys() {
+ final PrintWriter out = getOutPrintWriter();
+ final String modelLoadedKey = getNextArgRequired();
+ final String modelUnloadedKey = getNextArgRequired();
+ final String receiverPackageName = getNextArg();
+
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setModelBroadcastKeys(
+ new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration);
+ out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to "
+ + modelLoadedKey
+ + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey
+ + "\n and Package name set to : " + receiverPackageName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int setDeviceConfigNamespace() {
+ final PrintWriter out = getOutPrintWriter();
+ final String configNamespace = getNextArg();
+
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryDeviceConfigNamespace(configNamespace, duration);
+ out.println("OnDeviceIntelligence DeviceConfig Namespace temporarily set to "
+ + configNamespace
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+} \ No newline at end of file
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
new file mode 100644
index 000000000000..ac9747aa83b3
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
+ * logic set by the service implementation via a Secure Settings flag.
+ */
+public class RemoteOnDeviceIntelligenceService extends
+ ServiceConnector.Impl<IOnDeviceIntelligenceService> {
+ private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4);
+ private static final String TAG =
+ RemoteOnDeviceIntelligenceService.class.getSimpleName();
+
+ RemoteOnDeviceIntelligenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceIntelligenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceIntelligenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return LONG_TIMEOUT;
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30),
+ mContext.getUserId());
+ }
+}
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
new file mode 100644
index 000000000000..18b13838ea7c
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -0,0 +1,76 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles
+ * unbinding
+ * logic set by the service implementation via a SecureSettings flag.
+ */
+public class RemoteOnDeviceSandboxedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
+ private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1);
+
+ /**
+ * Creates an instance of {@link ServiceConnector}
+ *
+ * See {@code protected} methods for optional parameters you can override.
+ *
+ * @param context to be used for {@link Context#bindServiceAsUser binding} and
+ * {@link Context#unbindService unbinding}
+ * @param userId to be used for {@link Context#bindServiceAsUser binding}
+ */
+ RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceSandboxedInferenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return LONG_TIMEOUT;
+ }
+
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30),
+ mContext.getUserId());
+ }
+}
diff --git a/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
new file mode 100644
index 000000000000..32f0698a8f9c
--- /dev/null
+++ b/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.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.server.ondeviceintelligence.callbacks;
+
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback
+ * such that, in the case where the callback methods are not invoked, we do not have to wait for
+ * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in
+ * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
+ * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
+ * download will not complete and enabling faster cleanup.
+ */
+public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
+ private final IDownloadCallback callback;
+ private final Handler handler;
+ private final AndroidFuture future;
+ private final long idleTimeoutMs;
+
+ /**
+ * Constructor to create a ListenableDownloadCallback.
+ *
+ * @param callback callback to send download updates to caller.
+ * @param handler handler to schedule timeout runnable.
+ * @param future future to complete to signal the callback has reached a terminal state.
+ * @param idleTimeoutMs timeout within which download updates should be received.
+ */
+ public ListenableDownloadCallback(IDownloadCallback callback, Handler handler,
+ AndroidFuture future,
+ long idleTimeoutMs) {
+ this.callback = callback;
+ this.handler = handler;
+ this.future = future;
+ this.idleTimeoutMs = idleTimeoutMs;
+ handler.postDelayed(this,
+ idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked
+ }
+
+ @Override
+ public void onDownloadStarted(long bytesToDownload) throws RemoteException {
+ callback.onDownloadStarted(bytesToDownload);
+ handler.removeCallbacks(this);
+ handler.postDelayed(this, idleTimeoutMs);
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesDownloaded) throws RemoteException {
+ callback.onDownloadProgress(bytesDownloaded);
+ handler.removeCallbacks(this); // remove previously queued timeout tasks.
+ handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update.
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus,
+ String errorMessage, PersistableBundle errorParams) throws RemoteException {
+ callback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+ handler.removeCallbacks(this);
+ future.completeExceptionally(new TimeoutException());
+ }
+
+ @Override
+ public void onDownloadCompleted(
+ android.os.PersistableBundle downloadParams) throws RemoteException {
+ callback.onDownloadCompleted(downloadParams);
+ handler.removeCallbacks(this);
+ future.complete(null);
+ }
+
+ @Override
+ public void run() {
+ future.completeExceptionally(
+ new TimeoutException()); // complete the future as we haven't received updates
+ // for download progress.
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
index 63fe1b509751..e173c5e996df 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
@@ -25,13 +25,15 @@ import android.widget.Spinner;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceViewHolder;
+
import com.android.settingslib.widget.spinner.R;
/**
* This preference uses Spinner & SettingsSpinnerAdapter which provide default layouts for
* both view and drop down view of the Spinner.
*/
-public class SettingsSpinnerPreference extends Preference implements OnPreferenceClickListener {
+public class SettingsSpinnerPreference extends Preference
+ implements OnPreferenceClickListener, GroupSectionDividerMixin {
private SettingsSpinnerAdapter mAdapter;
private AdapterView.OnItemSelectedListener mListener;
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 5a4d3ce5661b..63c8929ef652 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -667,4 +667,25 @@
<item>3</item>
</string-array>
+ <!-- Options for showing shade on external display for developers -->
+ <string-array name="shade_display_awareness_entries" >
+ <item>Device display only (Default)</item>
+ <item>External display</item>
+ <item>Focus-based</item>
+ </string-array>
+
+ <!-- Options for showing shade on external display for developers -->
+ <string-array name="shade_display_awareness_summaries" >
+ <item>Show shade on device display only </item>
+ <item>Show device on single external display</item>
+ <item>Show device on last focused display</item>
+ </string-array>
+
+ <!-- Values for showing shade on external display for developers -->
+ <string-array name="shade_display_awareness_values" >
+ <item>device-display</item>
+ <item>external-display</item>
+ <item>focus-based</item>
+ </string-array>
+
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index eaf155df4785..e1929b725a58 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -990,6 +990,9 @@
<!-- UI debug setting: simulate secondary display devices using overlays [CHAR LIMIT=45] -->
<string name="overlay_display_devices_title">Simulate secondary displays</string>
+ <!-- UI debug setting: shade display awareness title [CHAR LIMIT=45] -->
+ <string name="shade_display_awareness_title">Shade display position</string>
+
<!-- Preference category for application debugging development settings. [CHAR LIMIT=25] -->
<string name="debug_applications_category">Apps</string>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9004488c2e12..c88a7fd834d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -182,6 +182,7 @@ public class SettingsBackupTest {
Settings.Global.DEVELOPMENT_FORCE_RTL,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
+ Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS,
Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DEVICE_IDLE_CONSTANTS,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 0694b6123c11..c6555041164d 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -257,6 +257,9 @@ public class BugreportProgressService extends Service {
/** Always keep remote bugreport files created in the last day. */
private static final long REMOTE_MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
+ /** Minimum delay for sending last update notification */
+ private static final int DELAY_NOTIFICATION_MS = 250;
+
private final Object mLock = new Object();
/** Managed bugreport info (keyed by id) */
@@ -927,6 +930,7 @@ public class BugreportProgressService extends Service {
Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
}
info.lastProgress.set(progress);
+ info.lastUpdate.set(System.currentTimeMillis());
sendForegroundabledNotification(info.id, builder.build());
}
@@ -1455,6 +1459,16 @@ public class BugreportProgressService extends Service {
*/
private void sendBugreportNotification(BugreportInfo info, boolean takingScreenshot) {
+ final long lastUpdate = System.currentTimeMillis() - info.lastUpdate.longValue();
+ if (lastUpdate < DELAY_NOTIFICATION_MS) {
+ Log.d(TAG, "Delaying final notification for "
+ + (DELAY_NOTIFICATION_MS - lastUpdate) + " ms ");
+ mMainThreadHandler.postDelayed(() -> {
+ sendBugreportNotification(info, takingScreenshot);
+ }, DELAY_NOTIFICATION_MS - lastUpdate);
+ return;
+ }
+
// Since adding the details can take a while, do it before notifying user.
addDetailsToZipFile(info);
@@ -1475,6 +1489,7 @@ public class BugreportProgressService extends Service {
final Notification.Builder builder = newBaseNotification(mContext)
.setContentTitle(title)
.setTicker(title)
+ .setProgress(100 /* max value of progress percentage */, 100, false)
.setOnlyAlertOnce(false)
.setContentText(content);
@@ -2743,7 +2758,6 @@ public class BugreportProgressService extends Service {
}
}
info.progress.set(progress);
- info.lastUpdate.set(System.currentTimeMillis());
updateProgress(info);
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 050a3704df1f..7bda2ea790b0 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -193,7 +193,7 @@ public class BugreportReceiverTest {
mService.mScreenshotDelaySec = SCREENSHOT_DELAY_SECONDS;
// Dup the fds which are passing to startBugreport function.
Mockito.doAnswer(invocation -> {
- final boolean isScreenshotRequested = invocation.getArgument(6);
+ final boolean isScreenshotRequested = invocation.getArgument(7);
if (isScreenshotRequested) {
mScreenshotFd = ParcelFileDescriptor.dup(invocation.getArgument(3));
}
@@ -250,7 +250,22 @@ public class BugreportReceiverTest {
mIDumpstateListener.onProgress(300);
assertProgressNotification(mProgressTitle, 99);
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId);
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId, 1);
+ assertActionSendMultiple(extras);
+
+ assertServiceNotRunning();
+ }
+
+ @Test
+ public void testStressProgress() throws Exception {
+ sendBugreportStarted();
+ waitForScreenshotButtonEnabled(true);
+
+ for (int i = 0; i <= 1000; i++) {
+ mIDumpstateListener.onProgress(i);
+ }
+ sendBugreportFinished();
+ Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId, 1);
assertActionSendMultiple(extras);
assertServiceNotRunning();
@@ -277,7 +292,7 @@ public class BugreportReceiverTest {
assertScreenshotButtonEnabled(false);
waitForScreenshotButtonEnabled(true);
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId);
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId, 2);
assertActionSendMultiple(extras, NO_NAME, NO_TITLE, NO_DESCRIPTION, 1);
assertServiceNotRunning();
@@ -294,7 +309,7 @@ public class BugreportReceiverTest {
// There's no indication in the UI about the screenshot finish, so just sleep like a baby...
sleep(SAFE_SCREENSHOT_DELAY * DateUtils.SECOND_IN_MILLIS);
- Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId);
+ Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId, 2);
assertActionSendMultiple(extras, NO_NAME, NO_TITLE, NO_DESCRIPTION, 1);
assertServiceNotRunning();
@@ -328,7 +343,7 @@ public class BugreportReceiverTest {
assertProgressNotification(NEW_NAME, 00.00f);
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE);
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE, 1);
assertActionSendMultiple(extras, NEW_NAME, TITLE, mDescription, 0);
assertServiceNotRunning();
@@ -363,7 +378,7 @@ public class BugreportReceiverTest {
assertProgressNotification(NEW_NAME, 00.00f);
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE);
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(TITLE, 1);
assertActionSendMultiple(extras, NEW_NAME, TITLE, mDescription, 0);
assertServiceNotRunning();
@@ -390,7 +405,7 @@ public class BugreportReceiverTest {
detailsUi.descField.setText(mDescription);
detailsUi.clickOk();
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId);
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(mBugreportId, 1);
assertActionSendMultiple(extras, NO_NAME, NO_TITLE, mDescription, 0);
assertServiceNotRunning();
@@ -441,7 +456,7 @@ public class BugreportReceiverTest {
detailsUi.clickOk();
// Finally, share bugreport.
- Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId);
+ Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId, 1);
assertActionSendMultiple(extras, NO_NAME, TITLE, mDescription, 0);
assertServiceNotRunning();
@@ -504,7 +519,7 @@ public class BugreportReceiverTest {
mUiBot.click(ok, "ok");
// Share the bugreport.
- mUiBot.chooseActivity(UI_NAME);
+ mUiBot.chooseActivity(UI_NAME, mContext, 1);
Bundle extras = mListener.getExtras();
assertActionSendMultiple(extras);
@@ -531,7 +546,7 @@ public class BugreportReceiverTest {
sendBugreportFinished();
killService();
assertServiceNotRunning();
- Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId);
+ Bundle extras = acceptBugreportAndGetSharedIntent(mBugreportId, 1);
assertActionSendMultiple(extras);
}
@@ -618,45 +633,49 @@ public class BugreportReceiverTest {
* Sends a "bugreport finished" event and waits for the result.
*
* @param id The bugreport id for finished notification string title substitution.
+ * @param count Number of files to be shared
* @return extras sent in the shared intent.
*/
- private Bundle sendBugreportFinishedAndGetSharedIntent(int id) throws Exception {
+ private Bundle sendBugreportFinishedAndGetSharedIntent(int id, int count) throws Exception {
sendBugreportFinished();
- return acceptBugreportAndGetSharedIntent(id);
+ return acceptBugreportAndGetSharedIntent(id, count);
}
/**
* Sends a "bugreport finished" event and waits for the result.
*
* @param notificationTitle The title of finished notification.
+ * @param count Number of files to be shared
* @return extras sent in the shared intent.
*/
- private Bundle sendBugreportFinishedAndGetSharedIntent(String notificationTitle)
+ private Bundle sendBugreportFinishedAndGetSharedIntent(String notificationTitle, int count)
throws Exception {
sendBugreportFinished();
- return acceptBugreportAndGetSharedIntent(notificationTitle);
+ return acceptBugreportAndGetSharedIntent(notificationTitle, count);
}
/**
* Accepts the notification to share the finished bugreport and waits for the result.
*
* @param id The bugreport id for finished notification string title substitution.
+ * @param count Number of files to be shared
* @return extras sent in the shared intent.
*/
- private Bundle acceptBugreportAndGetSharedIntent(int id) {
+ private Bundle acceptBugreportAndGetSharedIntent(int id, int count) {
final String notificationTitle = mContext.getString(R.string.bugreport_finished_title, id);
- return acceptBugreportAndGetSharedIntent(notificationTitle);
+ return acceptBugreportAndGetSharedIntent(notificationTitle, count);
}
/**
* Accepts the notification to share the finished bugreport and waits for the result.
*
* @param notificationTitle The title of finished notification.
+ * @param count Number of files to be shared
* @return extras sent in the shared intent.
*/
- private Bundle acceptBugreportAndGetSharedIntent(String notificationTitle) {
+ private Bundle acceptBugreportAndGetSharedIntent(String notificationTitle, int count) {
mUiBot.clickOnNotification(notificationTitle);
- mUiBot.chooseActivity(UI_NAME);
+ mUiBot.chooseActivity(UI_NAME, mContext, count);
return mListener.getExtras();
}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index ce9f70d8b977..60008a353d81 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -18,9 +18,12 @@ package com.android.shell;
import android.app.Instrumentation;
import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Resources;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.PluralsMessageFormatter;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
@@ -34,7 +37,9 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* A helper class for UI-related testing tasks.
@@ -206,11 +211,26 @@ final class UiBot {
*
* @param name name of the activity as displayed in the UI (typically the value set by
* {@code android:label} in the manifest).
+ * @param context Context of the target application
+ * @param count Number of files to be shared
*/
- public void chooseActivity(String name) {
+ public void chooseActivity(String name, Context context, int count) {
// It uses an intent chooser now, so just getting the activity by text is enough...
- final String share = mInstrumentation.getContext().getString(
- com.android.internal.R.string.share);
+ Resources res = null;
+ try {
+ res = context.getPackageManager()
+ .getResourcesForApplication("com.android.intentresolver");
+ } catch (Exception e) {
+ assertNotNull("could not get resources for com.android.intentresolver", res);
+ }
+ /* Resource read is defined as a string which contains a plural
+ * which needs some formatting */
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", count);
+ final String share = PluralsMessageFormatter.format(
+ res,
+ arguments,
+ res.getIdentifier("sharing_files", "string", "com.android.intentresolver"));
boolean gotIt = mDevice.wait(Until.hasObject(By.text(share)), mTimeout);
assertTrue("could not get share activity (" + share + ")", gotIt);
swipeUp();
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 777f6d35c1de..8cfae2bcc547 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1871,6 +1871,13 @@ flag {
}
flag {
+ name: "hub_edit_mode_touch_adjustments"
+ namespace: "systemui"
+ description: "Makes selected widget toggleable in edit mode and modifier buttons mutually exclusive."
+ bug: "383160667"
+}
+
+flag {
name: "glanceable_hub_direct_edit_mode"
namespace: "systemui"
description: "Invokes edit mode directly from long press in glanceable hub"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 85f549d43a11..55b42931b1fa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.composable
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.overscroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -101,6 +102,7 @@ private fun SceneScope.BouncerScene(
viewModel,
dialogFactory,
Modifier.element(Bouncer.Elements.Content)
+ .overscroll(verticalOverscrollEffect)
.sysuiResTag(Bouncer.TestTags.Root)
.fillMaxSize(),
)
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 315dc342dcd0..d5eaf829b746 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
@@ -296,9 +296,19 @@ fun CommunalHub(
offset.y,
) - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
- val key =
+ val tappedKey =
index?.let { keyAtIndexIfEditable(contentListState.list, index) }
- viewModel.setSelectedKey(key)
+
+ viewModel.setSelectedKey(
+ if (
+ Flags.hubEditModeTouchAdjustments() &&
+ selectedKey.value == tappedKey
+ ) {
+ null
+ } else {
+ tappedKey
+ }
+ )
}
}
}
@@ -1080,17 +1090,27 @@ private fun Toolbar(
.onSizeChanged { setToolbarSize(it) }
) {
val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
- ToolbarButton(
- isPrimary = !removeEnabled,
- modifier = Modifier.align(Alignment.CenterStart),
- onClick = onOpenWidgetPicker,
- ) {
- Icon(Icons.Default.Add, null)
- Text(text = addWidgetText)
+
+ if (!(Flags.hubEditModeTouchAdjustments() && removeEnabled)) {
+ ToolbarButton(
+ isPrimary = !removeEnabled,
+ modifier = Modifier.align(Alignment.CenterStart),
+ onClick = onOpenWidgetPicker,
+ ) {
+ Icon(Icons.Default.Add, null)
+ Text(text = addWidgetText)
+ }
}
AnimatedVisibility(
- modifier = Modifier.align(Alignment.Center),
+ modifier =
+ Modifier.align(
+ if (Flags.hubEditModeTouchAdjustments()) {
+ Alignment.CenterStart
+ } else {
+ Alignment.Center
+ }
+ ),
visible = removeEnabled,
enter = fadeIn(),
exit = fadeOut(),
@@ -1113,7 +1133,11 @@ private fun Toolbar(
horizontalArrangement =
Arrangement.spacedBy(
ButtonDefaults.IconSpacing,
- Alignment.CenterHorizontally,
+ if (Flags.hubEditModeTouchAdjustments()) {
+ Alignment.Start
+ } else {
+ Alignment.CenterHorizontally
+ },
),
verticalAlignment = Alignment.CenterVertically,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 364adcaffd77..5e9ade163ac2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -82,20 +82,15 @@ constructor(
Modifier.shortcutPadding()
} else {
Modifier
- }
+ },
)
}
}
}
@Composable
- fun SceneScope.IndicationArea(
- modifier: Modifier = Modifier,
- ) {
- Element(
- key = IndicationAreaElementKey,
- modifier = modifier.indicationAreaPadding(),
- ) {
+ fun SceneScope.IndicationArea(modifier: Modifier = Modifier) {
+ Element(key = IndicationAreaElementKey, modifier = modifier.indicationAreaPadding()) {
content {
IndicationArea(
indicationAreaViewModel = indicationAreaViewModel,
@@ -138,24 +133,20 @@ constructor(
ResourcesCompat.getDrawable(
context.resources,
R.drawable.keyguard_bottom_affordance_bg,
- context.theme
+ context.theme,
)
foreground =
ResourcesCompat.getDrawable(
context.resources,
R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
+ context.theme,
)
visibility = View.INVISIBLE
setPadding(padding, padding, padding, padding)
}
setBinding(
- binder.bind(
- view,
- viewModel,
- transitionAlpha,
- ) {
+ binder.bind(view, viewModel, transitionAlpha) {
indicationController.showTransientIndication(it)
}
)
@@ -164,10 +155,7 @@ constructor(
},
onRelease = { binding?.destroy() },
modifier =
- modifier.size(
- width = shortcutSizeDp().width,
- height = shortcutSizeDp().height,
- )
+ modifier.size(width = shortcutSizeDp().width, height = shortcutSizeDp().height),
)
}
@@ -182,6 +170,8 @@ constructor(
AndroidView(
factory = { context ->
val view = KeyguardIndicationArea(context, null)
+ view.isFocusable = true
+ view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
setDisposable(
KeyguardIndicationAreaBinder.bind(
view = view,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt
index 48067ce3c4a0..ef8911dae566 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt
@@ -29,7 +29,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.model.ShadeMode
@@ -42,7 +42,7 @@ import kotlinx.coroutines.launch
* transition.
*/
@Composable
-fun SceneScope.NotificationLockscreenScrim(
+fun ContentScope.NotificationLockscreenScrim(
viewModel: NotificationLockscreenScrimViewModel,
modifier: Modifier = Modifier,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index 94c18cdbef5a..cb87f0e7cf1c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -62,7 +62,6 @@ fun NotificationScrimNestedScrollConnection(
canStartPostScroll = { offsetAvailable, _, _ ->
offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
},
- canStartPostFling = { false },
onStart = { firstScroll ->
onStart(firstScroll)
object : ScrollController {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index d8abfd7a4b94..e1ee59ba0626 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -25,11 +25,13 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceAtLeast
import com.android.compose.nestedscroll.OnStopScope
@@ -80,9 +82,29 @@ fun Modifier.stackVerticalOverscroll(
}
return this.then(
- Modifier.nestedScroll(stackNestedScrollConnection).offset {
- IntOffset(x = 0, y = overscrollOffset.value.roundToInt())
- }
+ Modifier.nestedScroll(
+ remember {
+ object : NestedScrollConnection {
+ override suspend fun onPostFling(
+ consumed: Velocity,
+ available: Velocity,
+ ): Velocity {
+ return if (available.y < 0f && !canScrollForward()) {
+ overscrollOffset.animateTo(
+ targetValue = 0f,
+ initialVelocity = available.y,
+ animationSpec = tween(),
+ )
+ available
+ } else {
+ Velocity.Zero
+ }
+ }
+ }
+ }
+ )
+ .nestedScroll(stackNestedScrollConnection)
+ .offset { IntOffset(x = 0, y = overscrollOffset.value.roundToInt()) }
)
}
@@ -100,7 +122,6 @@ fun NotificationStackNestedScrollConnection(
canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
},
- canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
onStart = { firstScroll ->
onStart(firstScroll)
object : ScrollController {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ae273d8e2ad9..b54de784a202 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -45,6 +45,7 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
+import androidx.compose.foundation.overscroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -84,10 +85,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
-import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
@@ -134,7 +135,7 @@ private val quickSettingsShadeContentKey: ContentKey
* entire size of the scene.
*/
@Composable
-fun SceneScope.HeadsUpNotificationSpace(
+fun ContentScope.HeadsUpNotificationSpace(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
useHunBounds: () -> Boolean = { true },
@@ -176,7 +177,7 @@ fun SceneScope.HeadsUpNotificationSpace(
* the user. When swiped up, the heads up notification is snoozed.
*/
@Composable
-fun SceneScope.SnoozeableHeadsUpNotificationSpace(
+fun ContentScope.SnoozeableHeadsUpNotificationSpace(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
) {
@@ -246,7 +247,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace(
/** Adds the space where notification stack should appear in the scene. */
@Composable
-fun SceneScope.ConstrainedNotificationStack(
+fun ContentScope.ConstrainedNotificationStack(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
@@ -281,7 +282,7 @@ fun SceneScope.ConstrainedNotificationStack(
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
-fun SceneScope.NotificationScrollingStack(
+fun ContentScope.NotificationScrollingStack(
shadeSession: SaveableSession,
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
@@ -480,6 +481,7 @@ fun SceneScope.NotificationScrollingStack(
modifier =
modifier
.element(Notifications.Elements.NotificationScrim)
+ .overscroll(verticalOverscrollEffect)
.offset {
// if scrim is expanded while transitioning to Gone or QS scene, increase the
// offset in step with the corresponding transition so that it is 0 when it
@@ -622,7 +624,7 @@ fun SceneScope.NotificationScrollingStack(
* the notification contents (stack, footer, shelf) should be drawn.
*/
@Composable
-fun SceneScope.NotificationStackCutoffGuideline(
+fun ContentScope.NotificationStackCutoffGuideline(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
@@ -642,7 +644,7 @@ fun SceneScope.NotificationStackCutoffGuideline(
}
@Composable
-private fun SceneScope.NotificationPlaceholder(
+private fun ContentScope.NotificationPlaceholder(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
useStackBounds: () -> Boolean,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 5fb9416cf35b..e4f4df386583 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -37,6 +37,7 @@ import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotific
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.GoneUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -70,18 +71,22 @@ constructor(
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- val isIdle by remember {
- derivedStateOf { layoutState.transitionState is TransitionState.Idle }
+ val isIdleAndNotOccluded by remember {
+ derivedStateOf {
+ layoutState.transitionState is TransitionState.Idle &&
+ Overlays.NotificationsShade !in layoutState.transitionState.currentOverlays
+ }
}
- LaunchedEffect(isIdle) {
+ LaunchedEffect(isIdleAndNotOccluded) {
// Wait for being Idle on this Scene, otherwise LaunchedEffect would fire too soon,
// and another transition could override the NSSL stack bounds.
- if (isIdle) {
+ if (isIdleAndNotOccluded) {
// Reset the stack bounds to avoid caching these values from the previous Scenes,
// and not to confuse the StackScrollAlgorithm when it displays a HUN over GONE.
notificationStackScrolLView.get().apply {
- setStackTop(0f)
+ // use -headsUpInset to allow HUN translation outside bounds for snoozing
+ setStackTop(-getHeadsUpInset().toFloat())
setStackCutoff(0f)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 9de7a5d659ae..55fafd5cfeca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -33,7 +33,6 @@ import com.android.systemui.scene.ui.composable.transitions.notificationsShadeTo
import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
import com.android.systemui.scene.ui.composable.transitions.toNotificationsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShadeTransition
-import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
/**
@@ -134,27 +133,11 @@ val SceneContainerTransitions = transitions {
}
// Scene overscroll
-
+ // TODO(b/382477212) Remove STL Overscroll DSL
overscrollDisabled(Scenes.Gone, Orientation.Vertical)
overscrollDisabled(Scenes.Lockscreen, Orientation.Vertical)
- overscroll(Scenes.Bouncer, Orientation.Vertical) {
- translate(Bouncer.Elements.Content, y = { absoluteDistance })
- }
- overscroll(Scenes.Shade, Orientation.Vertical) {
- translate(
- Notifications.Elements.NotificationScrim,
- y = Shade.Dimensions.ScrimOverscrollLimit,
- )
- translate(Shade.Elements.SplitShadeStartColumn, y = Shade.Dimensions.ScrimOverscrollLimit)
- translate(
- Notifications.Elements.NotificationStackPlaceholder,
- y = Shade.Dimensions.ScrimOverscrollLimit,
- )
- }
- overscroll(Overlays.NotificationsShade, Orientation.Vertical) {
- translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
- }
- overscroll(Overlays.QuickSettingsShade, Orientation.Vertical) {
- translate(OverlayShade.Elements.Panel, y = OverlayShade.Dimensions.OverscrollLimit)
- }
+ overscrollDisabled(Scenes.Bouncer, Orientation.Vertical)
+ overscrollDisabled(Scenes.Shade, Orientation.Vertical)
+ overscrollDisabled(Overlays.NotificationsShade, Orientation.Vertical)
+ overscrollDisabled(Overlays.QuickSettingsShade, Orientation.Vertical)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 46f5ecd99301..8a5c96da5ac6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.overscroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -65,7 +66,10 @@ fun SceneScope.OverlayShade(
Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) {
Panel(
- modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(),
+ modifier =
+ Modifier.element(OverlayShade.Elements.Panel)
+ .overscroll(verticalOverscrollEffect)
+ .panelSize(),
content = content,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 22b6dbcf41ec..79fd1d7ddd8f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -39,6 +39,7 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.overscroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
@@ -527,6 +528,7 @@ private fun SceneScope.SplitShade(
Box(
modifier =
Modifier.element(Shade.Elements.SplitShadeStartColumn)
+ .overscroll(verticalOverscrollEffect)
.weight(1f)
.graphicsLayer { translationX = unfoldTranslationXForStartSide }
) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 2d589f37f3cb..974442494181 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.content.Content
-import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.animation.scene.content.state.TransitionState.DirectionProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
@@ -108,7 +108,7 @@ internal class DraggableHandlerImpl(
swipes.updateSwipesResults(fromContent)
val result =
- swipes.findUserActionResult(overSlop)
+ (if (overSlop < 0f) swipes.upOrLeftResult else swipes.downOrRightResult)
// As we were unable to locate a valid target scene, the initial SwipeAnimation
// cannot be defined. Consequently, a simple NoOp Controller will be returned.
?: return NoOpDragController
@@ -448,27 +448,6 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol
this.upOrLeftResult = upOrLeftResult
this.downOrRightResult = downOrRightResult
}
-
- /**
- * Returns the [UserActionResult] in the direction of [directionOffset].
- *
- * @param directionOffset signed float that indicates the direction. Positive is down or right
- * negative is up or left.
- * @return null when there are no targets in either direction. If one direction is null and you
- * drag into the null direction this function will return the opposite direction, assuming
- * that the users intention is to start the drag into the other direction eventually. If
- * [directionOffset] is 0f and both direction are available, it will default to
- * [upOrLeftResult].
- */
- fun findUserActionResult(directionOffset: Float): UserActionResult? {
- return when {
- upOrLeftResult == null && downOrRightResult == null -> null
- (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
- upOrLeftResult
-
- else -> downOrRightResult
- }
- }
}
internal class NestedScrollHandlerImpl(
@@ -536,31 +515,6 @@ internal class NestedScrollHandlerImpl(
}
}
},
- canStartPostFling = { velocityAvailable ->
- val behavior: NestedScrollBehavior =
- when {
- velocityAvailable > 0f -> topOrLeftBehavior
- velocityAvailable < 0f -> bottomOrRightBehavior
- else -> return@PriorityNestedScrollConnection false
- }
-
- // We could start an overscroll animation
- canChangeScene = false
-
- val pointersDown: PointersInfo.PointersDown? =
- when (val info = pointersInfoOwner.pointersInfo()) {
- PointersInfo.MouseWheel -> {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
-
- is PointersInfo.PointersDown -> info
- null -> null
- }
- lastPointersDown = pointersDown
-
- behavior.canStartOnPostFling && shouldEnableSwipes()
- },
onStart = { firstScroll ->
scrollController(
dragController =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e819bfd18578..07a19d83c995 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -1257,7 +1257,7 @@ private inline fun <T> computeValue(
}
val currentContent = currentContentState.content
- if (transition is TransitionState.HasOverscrollProperties) {
+ if (transition is TransitionState.DirectionProperties) {
val overscroll = transition.currentOverscrollSpec
if (overscroll?.content == currentContent) {
val elementSpec =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 955be603efaf..9622fc151bb7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -32,7 +32,7 @@ import androidx.compose.ui.platform.InspectorInfo
* not consumed by the [SceneTransitionLayout] unless specifically requested via
* [nestedScrollToScene].
*/
-enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
+enum class NestedScrollBehavior {
/**
* Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
* gesture begins at the edge of the scrollable component (so that a scroll in that direction
@@ -42,7 +42,7 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- EdgeNoPreview(canStartOnPostFling = false),
+ EdgeNoPreview,
/**
* Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
@@ -52,7 +52,7 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- EdgeWithPreview(canStartOnPostFling = true),
+ @Deprecated("This will be removed, see b/378470603") EdgeWithPreview,
/**
* Any overscroll will be used by the [SceneTransitionLayout] to move to the next scene.
@@ -60,7 +60,7 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
* In addition, during scene transitions, scroll events are consumed by the
* [SceneTransitionLayout] instead of the scrollable component.
*/
- EdgeAlways(canStartOnPostFling = true);
+ EdgeAlways;
companion object {
val Default = EdgeNoPreview
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 86c5fd824d8f..e8b2b09da377 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -382,7 +382,7 @@ internal class MutableSceneTransitionLayoutStateImpl(
// Compute the [TransformationSpec] when the transition starts.
val fromContent = transition.fromContent
val toContent = transition.toContent
- val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
+ val orientation = (transition as? TransitionState.DirectionProperties)?.orientation
// Update the transition specs.
transition.transformationSpec =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 59d0b55c1db8..5aaeda84edf0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -25,7 +25,7 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.animation.scene.content.state.TransitionState.DirectionProperties.Companion.DistanceUnspecified
import kotlin.math.absoluteValue
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch
@@ -197,7 +197,7 @@ internal class SwipeAnimation<T : ContentKey>(
private val distance: (SwipeAnimation<T>) -> Float,
currentContent: T = fromContent,
dragOffset: Float = 0f,
-) : TransitionState.HasOverscrollProperties {
+) : TransitionState.DirectionProperties {
/** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */
lateinit var contentTransition: TransitionState.Transition
@@ -513,7 +513,7 @@ private class ChangeSceneSwipeTransition(
swipeAnimation.toContent,
replacedTransition,
),
- TransitionState.HasOverscrollProperties by swipeAnimation {
+ TransitionState.DirectionProperties by swipeAnimation {
constructor(
other: ChangeSceneSwipeTransition
@@ -575,7 +575,7 @@ private class ShowOrHideOverlaySwipeTransition(
swipeAnimation.toContent,
replacedTransition,
),
- TransitionState.HasOverscrollProperties by swipeAnimation {
+ TransitionState.DirectionProperties by swipeAnimation {
constructor(
other: ShowOrHideOverlaySwipeTransition
) : this(
@@ -634,7 +634,7 @@ private class ReplaceOverlaySwipeTransition(
swipeAnimation.toContent,
replacedTransition,
),
- TransitionState.HasOverscrollProperties by swipeAnimation {
+ TransitionState.DirectionProperties by swipeAnimation {
constructor(
other: ReplaceOverlaySwipeTransition
) : this(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 48f08a7086d6..952668ab49ff 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -111,6 +111,9 @@ interface SceneTransitionsBuilder {
* The overscroll animation always starts from a progress of 0f, and reaches 1f when moving the
* [distance] down/right, -1f when moving in the opposite direction.
*/
+ @Deprecated(
+ "Use verticalOverscrollEffect (or horizontalOverscrollEffect) directly from SceneScope."
+ )
fun overscroll(
content: ContentKey,
orientation: Orientation,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index d66fe42084de..29be445e82bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -273,7 +273,7 @@ sealed interface TransitionState {
* every time progress is changed.
*/
private val _currentOverscrollSpec: State<OverscrollSpecImpl?>? =
- if (this !is HasOverscrollProperties) {
+ if (this !is DirectionProperties) {
null
} else {
derivedStateOf {
@@ -406,7 +406,7 @@ sealed interface TransitionState {
/** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
internal fun isWithinProgressRange(progress: Float): Boolean {
// If the properties are missing we assume that every [Transition] can overscroll
- if (this !is HasOverscrollProperties) return true
+ if (this !is DirectionProperties) return true
// [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet.
val specForCurrentScene =
when {
@@ -444,7 +444,7 @@ sealed interface TransitionState {
}
}
- interface HasOverscrollProperties {
+ interface DirectionProperties {
/**
* The position of the [Transition.toContent].
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index bfb5ca733d90..944bd85991c9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -157,7 +157,7 @@ private class VerticalContainerRevealSizeTransformation(
val idleSize = checkNotNull(element.targetSize(content))
val userActionDistance = idleSize.height
val progress =
- when ((transition as? TransitionState.HasOverscrollProperties)?.bouncingContent) {
+ when ((transition as? TransitionState.DirectionProperties)?.bouncingContent) {
null -> transition.progressTo(content)
content -> 1f
else -> 0f
@@ -256,7 +256,7 @@ private class ContainerRevealAlphaTransformation(
private fun targetAlpha(transition: TransitionState.Transition, content: ContentKey): Float {
if (transition.isUserInputOngoing) {
- if (transition !is TransitionState.HasOverscrollProperties) {
+ if (transition !is TransitionState.DirectionProperties) {
error(
"Unsupported transition driven by user input but that does not have " +
"overscroll properties: $transition"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 2f4d5bff8b41..432add38385a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -60,7 +60,7 @@ private constructor(
// As this object is created by OverscrollBuilderImpl and we retrieve the current
// OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
// that this method was invoked after performing this check.
- val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+ val overscrollProperties = transition as TransitionState.DirectionProperties
val overscrollScope =
cachedOverscrollScope.getFromCacheOrCompute(density = this, overscrollProperties)
@@ -77,17 +77,17 @@ private constructor(
/**
* A helper class to cache a [OverscrollScope] given a [Density] and
- * [TransitionState.HasOverscrollProperties]. This helps avoid recreating a scope every frame
- * whenever an overscroll transition is computed.
+ * [TransitionState.DirectionProperties]. This helps avoid recreating a scope every frame whenever
+ * an overscroll transition is computed.
*/
private class CachedOverscrollScope {
private var previousScope: OverscrollScope? = null
private var previousDensity: Density? = null
- private var previousOverscrollProperties: TransitionState.HasOverscrollProperties? = null
+ private var previousOverscrollProperties: TransitionState.DirectionProperties? = null
fun getFromCacheOrCompute(
density: Density,
- overscrollProperties: TransitionState.HasOverscrollProperties,
+ overscrollProperties: TransitionState.DirectionProperties,
): OverscrollScope {
if (
previousScope == null ||
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 98a00173f1d7..b26bf55c85ec 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -56,7 +56,6 @@ fun LargeTopAppBarNestedScrollConnection(
canStartPostScroll = { offsetAvailable, _, _ ->
offsetAvailable > 0 && height() < maxHeight()
},
- canStartPostFling = { false },
onStart = {
LargeTopAppBarScrollController(
height = height,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 3f182363e20c..3d0f182fffee 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -24,7 +24,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.android.compose.ui.util.SpaceVectorConverter
-import kotlin.math.sign
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
@@ -102,8 +101,8 @@ interface OnStopScope {
* over the default nested scrolling logic.
*
* When started, this connection intercepts scroll events *before* they reach child composables.
- * This "priority mode" is activated activated when either [canStartPreScroll], [canStartPostScroll]
- * or [canStartPostFling] returns `true`.
+ * This "priority mode" is activated when either [canStartPreScroll] or [canStartPostScroll] returns
+ * `true`.
*
* Once started, the [onStart] lambda provides a [ScrollController] to manage the scrolling. This
* controller allows you to directly manipulate the scroll state and define how scroll events are
@@ -123,8 +122,6 @@ interface OnStopScope {
* @param canStartPostScroll A lambda that returns `true` if the connection should enter priority
* mode during the post-scroll phase. This is called after child connections have consumed the
* scroll.
- * @param canStartPostFling A lambda that returns `true` if the connection should enter priority
- * mode during the post-fling phase. This is called after a fling gesture has been initiated.
* @param onStart A lambda that is called when the connection enters priority mode. It should return
* a [ScrollController] that will be used to control the scroll.
* @sample LargeTopAppBarNestedScrollConnection
@@ -136,7 +133,6 @@ class PriorityNestedScrollConnection(
(offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
private val canStartPostScroll:
(offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
- private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
private val onStart: (firstScroll: Float) -> ScrollController,
) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
@@ -233,17 +229,6 @@ class PriorityNestedScrollConnection(
return stop(velocity = availableFloat)
}
- // Check if post-fling condition is met, and start priority mode if necessary.
- // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
- // overscroll behavior on the Scene level.
- if (canStartPostFling(availableFloat)) {
- // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of
- // 1px given the available velocity.
- val smallOffset = availableFloat.sign
- start(availableOffset = smallOffset)
- return stop(availableFloat)
- }
-
// Reset offset tracking after the fling gesture is finished.
resetOffsetTracker()
return Velocity.Zero
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index b20056d54de1..6985644579f6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -19,7 +19,9 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.overscroll
import androidx.compose.material3.Text
+import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
@@ -102,7 +104,7 @@ class DraggableHandlerTest {
userActions =
mapOf(Swipe.Up to SceneB, Swipe.Up(fromSource = Edge.Bottom) to SceneA),
) {
- Text("SceneC")
+ Text("SceneC", Modifier.overscroll(verticalOverscrollEffect))
}
overlay(
key = OverlayA,
@@ -434,35 +436,12 @@ class DraggableHandlerTest {
}
@Test
- fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest {
+ fun onDragIntoNoAction_stayIdle() = runGestureTest {
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- val dragController = onDragStarted(overSlop = 10f)
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = -0.1f,
- )
-
- // Reverse drag direction, it will consume the previous drag
- dragController.onDragDelta(pixels = -10f)
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.0f,
- )
-
- // Continue reverse drag direction, it should record progress to Scene B
- dragController.onDragDelta(pixels = -10f)
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.1f,
- )
+ onDragStarted(overSlop = 10f, expectedConsumedOverSlop = 0f)
+ assertIdle(currentScene = SceneC)
}
@Test
@@ -942,30 +921,6 @@ class DraggableHandlerTest {
}
@Test
- fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
- layoutState.transitions = transitions {
- overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
- }
- // Start at scene C.
- navigateToSceneC()
-
- val scene = layoutState.transitionState.currentScene
- // We should have overscroll spec for scene C
- assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull()
- assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNull()
-
- val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
- nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
-
- // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
- assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
- assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
- val transition = layoutState.currentTransition
- assertThat(transition).isNotNull()
- assertThat(transition!!.progress).isEqualTo(-0.1f)
- }
-
- @Test
fun nestedScrollUseFromSourceInfo() = runGestureTest {
// Start at scene C.
navigateToSceneC()
@@ -1229,72 +1184,6 @@ class DraggableHandlerTest {
}
@Test
- fun overscroll_releaseAtNegativePercent_up() = runGestureTest {
- // Make Scene A overscrollable.
- layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
- overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
- }
-
- mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
-
- val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
- val transition = assertThat(transitionState).isSceneTransition()
- assertThat(transition).hasFromScene(SceneA)
- assertThat(transition).hasToScene(SceneB)
- assertThat(transition).hasProgress(-1f)
-
- // Release to A.
- dragController.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = {
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = -1f)
- },
- expectedConsumedVelocity = 0f,
- )
-
- // We kept the overscroll at 100% so that the placement logic didn't change at the end of
- // the animation.
- assertIdle(SceneA)
- assertThat(transition).hasProgress(0f)
- assertThat(transition).hasOverscrollSpec()
- }
-
- @Test
- fun overscroll_releaseAtNegativePercent_down() = runGestureTest {
- // Make Scene A overscrollable.
- layoutState.transitions = transitions {
- from(SceneA, to = SceneC) { spec = spring(dampingRatio = Spring.DampingRatioNoBouncy) }
- overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
- }
-
- mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
-
- val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
- val transition = assertThat(transitionState).isSceneTransition()
- assertThat(transition).hasFromScene(SceneA)
- assertThat(transition).hasToScene(SceneC)
- assertThat(transition).hasProgress(-1f)
-
- // Release to A.
- dragController.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = {
- assertTransition(fromScene = SceneA, toScene = SceneC, progress = -1f)
- },
- expectedConsumedVelocity = 0f,
- )
-
- // We kept the overscroll at 100% so that the placement logic didn't change at the end of
- // the animation.
- assertIdle(SceneA)
- assertThat(transition).hasProgress(0f)
- assertThat(transition).hasOverscrollSpec()
- }
-
- @Test
fun requireFullDistanceSwipe() = runGestureTest {
mutableUserActionsA +=
Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 4410e157b526..ffba63988cfc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -1006,77 +1006,74 @@ class ElementTest {
@Test
fun elementTransitionDuringNestedScrollOverscroll() {
+ lateinit var density: Density
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val overscrollTranslateY = 10.dp
val layoutWidth = 200.dp
val layoutHeight = 400.dp
val state =
rule.runOnUiThread {
MutableSceneTransitionLayoutState(
- initialScene = SceneB,
- transitions =
- transitions {
- overscroll(SceneB, Orientation.Vertical) {
- progressConverter = ProgressConverter.linear()
- translate(TestElements.Foo, y = overscrollTranslateY)
- }
- },
+ initialScene = SceneA,
+ transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) },
)
- as MutableSceneTransitionLayoutStateImpl
}
rule.setContent {
+ density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
SceneTransitionLayout(
state = state,
modifier = Modifier.size(layoutWidth, layoutHeight),
) {
- scene(SceneA) { Spacer(Modifier.fillMaxSize()) }
- scene(SceneB, userActions = mapOf(Swipe.Up to SceneA)) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
Box(
Modifier
// A scrollable that does not consume the scroll gesture
.scrollable(
- rememberScrollableState(consumeScrollDelta = { 0f }),
- Orientation.Vertical,
+ state = rememberScrollableState(consumeScrollDelta = { 0f }),
+ orientation = Orientation.Vertical,
)
.fillMaxSize()
- ) {
- Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
- }
+ )
+ }
+ scene(SceneB) {
+ Spacer(
+ Modifier.overscroll(verticalOverscrollEffect)
+ .element(TestElements.Foo)
+ .fillMaxSize()
+ )
}
}
}
assertThat(state.transitionState).isIdle()
- val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag)
- fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertDoesNotExist()
// Swipe by half of verticalSwipeDistance.
rule.onRoot().performTouchInput {
val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
down(middleTop)
- // Scroll 50%
+ // Scroll 50%.
moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
}
val transition = assertThat(state.transitionState).isSceneTransition()
- assertThat(transition).hasOverscrollSpec()
- assertThat(transition).hasProgress(-0.5f)
- fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
+ assertThat(transition).hasProgress(0.5f)
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
rule.onRoot().performTouchInput {
- // Scroll another 100%
+ // Scroll another 100%.
moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
}
- // Scroll 150% (Scene B overscroll by 50%)
- assertThat(transition).hasProgress(-1.5f)
- assertThat(transition).hasOverscrollSpec()
- fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
+ // Scroll 150% (Scene B overscroll by 50%).
+ assertThat(transition).hasProgress(1f)
+ rule
+ .onNodeWithTag(TestElements.Foo.testTag)
+ .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density))
}
@Test
@@ -2856,7 +2853,7 @@ class ElementTest {
// Start an overscrollable transition driven by progress.
var progress by mutableFloatStateOf(0f)
val transition = transition(from = SceneA, to = SceneB, progress = { progress })
- assertThat(transition).isInstanceOf(TransitionState.HasOverscrollProperties::class.java)
+ assertThat(transition).isInstanceOf(TransitionState.DirectionProperties::class.java)
scope.launch { state.startTransition(transition) }
// Reset the counters after the first animation frame.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 0adb4809dd2d..9a2af640c46f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -168,12 +168,12 @@ abstract class BaseTransitionSubject<T : TransitionState.Transition>(
fun hasBouncingContent(content: ContentKey) {
val actual = actual
- if (actual !is TransitionState.HasOverscrollProperties) {
+ if (actual !is TransitionState.DirectionProperties) {
failWithActual(simpleFact("expected to be ContentState.HasOverscrollProperties"))
}
check("bouncingContent")
- .that((actual as TransitionState.HasOverscrollProperties).bouncingContent)
+ .that((actual as TransitionState.DirectionProperties).bouncingContent)
.isEqualTo(content)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 28ea2d239b54..51483a894e1e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -39,7 +39,6 @@ import org.junit.runner.RunWith
class PriorityNestedScrollConnectionTest {
private var canStartPreScroll = false
private var canStartPostScroll = false
- private var canStartPostFling = false
private var canStopOnPreFling = true
private var isStarted = false
private var lastScroll: Float? = null
@@ -63,7 +62,6 @@ class PriorityNestedScrollConnectionTest {
orientation = Orientation.Vertical,
canStartPreScroll = { _, _, _ -> canStartPreScroll },
canStartPostScroll = { _, _, _ -> canStartPostScroll },
- canStartPostFling = { canStartPostFling },
onStart = { _ ->
isStarted = true
object : ScrollController {
@@ -239,36 +237,6 @@ class PriorityNestedScrollConnectionTest {
}
@Test
- fun receive_onPostFling() = runTest {
- canStartPostFling = true
-
- scrollConnection.onPostFling(consumed = Velocity(1f, 1f), available = Velocity(2f, 2f))
-
- assertThat(lastStop).isEqualTo(2f)
- }
-
- @Test
- fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
- canStartPostFling = true
-
- scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
- assertThat(isStarted).isEqualTo(false)
-
- scrollConnection.onPostScroll(
- consumed = Offset.Zero,
- available = Offset.Zero,
- source = UserInput,
- )
- assertThat(isStarted).isEqualTo(false)
-
- scrollConnection.onPreFling(available = Velocity.Zero)
- assertThat(isStarted).isEqualTo(false)
-
- scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
- assertThat(isStarted).isEqualTo(true)
- }
-
- @Test
fun handleMultipleOnPreFlingCalls() = runTest {
startPriorityModePostScroll()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt
index 646cff8b944c..6015479d8e21 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt
@@ -71,7 +71,7 @@ fun transition(
): TestOverlayTransition {
return object :
TestOverlayTransition(fromScene, overlay, replacedTransition),
- TransitionState.HasOverscrollProperties {
+ TransitionState.DirectionProperties {
override val isEffectivelyShown: Boolean
get() = isEffectivelyShown()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestReplaceOverlayTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestReplaceOverlayTransition.kt
index c342f488212a..bd2118dd8395 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestReplaceOverlayTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestReplaceOverlayTransition.kt
@@ -68,7 +68,7 @@ fun transition(
): TestReplaceOverlayTransition {
return object :
TestReplaceOverlayTransition(from, to, replacedTransition),
- TransitionState.HasOverscrollProperties {
+ TransitionState.DirectionProperties {
override val effectivelyShownOverlay: OverlayKey
get() = effectivelyShownOverlay()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt
index d24b895c3050..1d27e3a3f191 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt
@@ -62,7 +62,7 @@ fun transition(
replacedTransition: Transition? = null,
): TestSceneTransition {
return object :
- TestSceneTransition(from, to, replacedTransition), TransitionState.HasOverscrollProperties {
+ TestSceneTransition(from, to, replacedTransition), TransitionState.DirectionProperties {
override val currentScene: SceneKey
get() = current()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
deleted file mode 100644
index ce57fe256798..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static android.view.View.INVISIBLE;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.plugins.clocks.ClockAnimations;
-import com.android.systemui.plugins.clocks.ClockController;
-import com.android.systemui.plugins.clocks.ClockEvents;
-import com.android.systemui.plugins.clocks.ClockFaceConfig;
-import com.android.systemui.plugins.clocks.ClockFaceController;
-import com.android.systemui.plugins.clocks.ClockFaceEvents;
-import com.android.systemui.plugins.clocks.ClockTickRate;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.shared.clocks.ClockRegistry;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
-import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase {
-
- @Mock
- protected KeyguardClockSwitch mView;
- @Mock
- protected StatusBarStateController mStatusBarStateController;
- @Mock
- protected ClockRegistry mClockRegistry;
- @Mock
- KeyguardSliceViewController mKeyguardSliceViewController;
- @Mock
- LockscreenSmartspaceController mSmartspaceController;
-
- @Mock
- Resources mResources;
- @Mock
- KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
- @Mock
- protected ClockController mClockController;
- @Mock
- protected ClockFaceController mLargeClockController;
- @Mock
- protected ClockFaceController mSmallClockController;
- @Mock
- protected ClockAnimations mClockAnimations;
- @Mock
- protected ClockEvents mClockEvents;
- @Mock
- protected ClockFaceEvents mClockFaceEvents;
- @Mock
- DumpManager mDumpManager;
- @Mock
- ClockEventController mClockEventController;
-
- @Mock
- protected NotificationIconContainer mNotificationIcons;
- @Mock
- protected AnimatableClockView mSmallClockView;
- @Mock
- protected AnimatableClockView mLargeClockView;
- @Mock
- protected FrameLayout mSmallClockFrame;
- @Mock
- protected FrameLayout mLargeClockFrame;
- @Mock
- protected SecureSettings mSecureSettings;
- @Mock
- protected LogBuffer mLogBuffer;
-
- @Mock
- protected KeyguardClockInteractor mKeyguardClockInteractor;
-
- protected final View mFakeDateView = (View) (new ViewGroup(mContext) {
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {}
- });
- protected final View mFakeWeatherView = new View(mContext);
- protected final View mFakeSmartspaceView = new View(mContext);
-
- protected KeyguardClockSwitchController mController;
- protected View mSliceView;
- protected LinearLayout mStatusArea;
- protected FakeExecutor mExecutor;
- protected FakeFeatureFlags mFakeFeatureFlags;
- @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mFakeDateView.setTag(R.id.tag_smartspace_view, new Object());
- mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object());
- mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object());
-
- when(mView.findViewById(R.id.left_aligned_notification_icon_container))
- .thenReturn(mNotificationIcons);
- when(mNotificationIcons.getLayoutParams()).thenReturn(
- mock(RelativeLayout.LayoutParams.class));
- when(mView.getContext()).thenReturn(getContext());
- when(mView.getResources()).thenReturn(mResources);
- when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
- .thenReturn(100);
- when(mResources.getDimensionPixelSize(com.android.systemui.customization.R.dimen.keyguard_large_clock_top_margin))
- .thenReturn(-200);
- when(mResources.getInteger(com.android.internal.R.integer.config_doublelineClockDefault))
- .thenReturn(1);
- when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
- .thenReturn(INVISIBLE);
-
- when(mView
- .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large))
- .thenReturn(mLargeClockFrame);
- when(mView
- .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view))
- .thenReturn(mSmallClockFrame);
- when(mSmallClockView.getContext()).thenReturn(getContext());
- when(mLargeClockView.getContext()).thenReturn(getContext());
-
- when(mView.isAttachedToWindow()).thenReturn(true);
- when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
- when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
- when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
- mExecutor = new FakeExecutor(new FakeSystemClock());
- mFakeFeatureFlags = new FakeFeatureFlags();
- mController = new KeyguardClockSwitchController(
- mView,
- mStatusBarStateController,
- mClockRegistry,
- mKeyguardSliceViewController,
- mSmartspaceController,
- mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
- mKeyguardUnlockAnimationController,
- mSecureSettings,
- mExecutor,
- mExecutor,
- mDumpManager,
- mClockEventController,
- mLogBuffer,
- KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
- mKeyguardClockInteractor,
- mock(InWindowLauncherUnlockAnimationManager.class)
- );
-
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mLargeClockController.getView()).thenReturn(mLargeClockView);
- when(mSmallClockController.getView()).thenReturn(mSmallClockView);
- when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
- when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
- when(mClockController.getEvents()).thenReturn(mClockEvents);
- when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
- when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
- when(mLargeClockController.getAnimations()).thenReturn(mClockAnimations);
- when(mSmallClockController.getAnimations()).thenReturn(mClockAnimations);
- when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
- when(mClockEventController.getClock()).thenReturn(mClockController);
- when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
- when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
-
- mSliceView = new View(getContext());
- when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
- mStatusArea = new LinearLayout(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
- }
-
- private void removeView(View v) {
- ViewGroup group = ((ViewGroup) v.getParent());
- if (group != null) {
- group.removeView(v);
- }
- }
-
- protected void init() {
- mController.init();
-
- verify(mView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachCaptor.getValue().onViewAttachedToWindow(mView);
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
deleted file mode 100644
index 892375d002c1..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.database.ContentObserver;
-import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.provider.Settings;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.plugins.clocks.ClockFaceConfig;
-import com.android.systemui.plugins.clocks.ClockTickRate;
-import com.android.systemui.shared.clocks.ClockRegistry;
-import com.android.systemui.statusbar.StatusBarState;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.verification.VerificationMode;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest {
- @Test
- public void testInit_viewAlreadyAttached() {
- mController.init();
-
- verifyAttachment(times(1));
- }
-
- @Test
- public void testInit_viewNotYetAttached() {
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-
- when(mView.isAttachedToWindow()).thenReturn(false);
- mController.init();
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- verifyAttachment(never());
-
- listenerArgumentCaptor.getValue().onViewAttachedToWindow(mView);
-
- verifyAttachment(times(1));
- }
-
- @Test
- public void testInitSubControllers() {
- mController.init();
- verify(mKeyguardSliceViewController).init();
- }
-
- @Test
- public void testInit_viewDetached() {
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- mController.init();
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- verifyAttachment(times(1));
-
- listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mClockEventController).unregisterListeners();
- }
-
- @Test
- public void testPluginPassesStatusBarState() {
- ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
-
- mController.init();
- verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
-
- listenerArgumentCaptor.getValue().onCurrentClockChanged();
- verify(mView, times(2)).setClock(mClockController, StatusBarState.SHADE);
- verify(mClockEventController, times(2)).setClock(mClockController);
- }
-
- @Test
- public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- mController.init();
-
- assertEquals(View.GONE, mSliceView.getVisibility());
- }
-
- @Test
- public void onLocaleListChangedRebuildsSmartspaceView() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- mController.init();
-
- mController.onLocaleListChanged();
- // Should be called once on initial setup, then once again for locale change
- verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
- }
-
- @Test
- public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
- mController.init();
-
- mController.onLocaleListChanged();
- // Should be called once on initial setup, then once again for locale change
- verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
- verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
- verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
- }
-
- @Test
- public void testSmartspaceDisabledShowsKeyguardStatusArea() {
- when(mSmartspaceController.isEnabled()).thenReturn(false);
- mController.init();
-
- assertEquals(View.VISIBLE, mSliceView.getVisibility());
- }
-
- @Test
- public void testRefresh() {
- mController.refresh();
-
- verify(mSmartspaceController).requestSmartspaceUpdate();
- }
-
- @Test
- public void testChangeToDoubleLineClockSetsSmallClock() {
- when(mSecureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1,
- UserHandle.USER_CURRENT))
- .thenReturn(0);
- ArgumentCaptor<ContentObserver> observerCaptor =
- ArgumentCaptor.forClass(ContentObserver.class);
- mController.init();
- mExecutor.runAllReady();
- verify(mSecureSettings).registerContentObserverForUserSync(
- eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
- anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
- ContentObserver observer = observerCaptor.getValue();
- mExecutor.runAllReady();
-
- // When a settings change has occurred to the small clock, make sure the view is adjusted
- reset(mView);
- when(mView.getResources()).thenReturn(mResources);
- observer.onChange(true);
- mExecutor.runAllReady();
- verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
- }
-
- @Test
- public void testGetClock_ForwardsToClock() {
- assertEquals(mClockController, mController.getClock());
- }
-
- @Test
- public void testGetLargeClockBottom_returnsExpectedValue() {
- when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE);
- when(mLargeClockFrame.getHeight()).thenReturn(100);
- when(mSmallClockFrame.getHeight()).thenReturn(50);
- when(mLargeClockView.getHeight()).thenReturn(40);
- when(mSmallClockView.getHeight()).thenReturn(20);
- mController.init();
-
- assertEquals(170, mController.getClockBottom(1000));
- }
-
- @Test
- public void testGetSmallLargeClockBottom_returnsExpectedValue() {
- when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE);
- when(mLargeClockFrame.getHeight()).thenReturn(100);
- when(mSmallClockFrame.getHeight()).thenReturn(50);
- when(mLargeClockView.getHeight()).thenReturn(40);
- when(mSmallClockView.getHeight()).thenReturn(20);
- mController.init();
-
- assertEquals(1120, mController.getClockBottom(1000));
- }
-
- @Test
- public void testGetClockBottom_nullClock_returnsZero() {
- when(mClockEventController.getClock()).thenReturn(null);
- assertEquals(0, mController.getClockBottom(10));
- }
-
- @Test
- public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() {
- when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
- ArgumentCaptor<ContentObserver> observerCaptor =
- ArgumentCaptor.forClass(ContentObserver.class);
- mController.init();
- mExecutor.runAllReady();
- verify(mSecureSettings).registerContentObserverForUserSync(
- eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
- observerCaptor.capture(), eq(UserHandle.USER_ALL));
- ContentObserver observer = observerCaptor.getValue();
- mExecutor.runAllReady();
- // When a settings change has occurred, check that view is visible.
- observer.onChange(true);
- mExecutor.runAllReady();
- assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
- }
-
- @Test
- public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
- ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
- when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
- mController.init();
- mExecutor.runAllReady();
- assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
-
- when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
- when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
- verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
- listenerArgumentCaptor.getValue().onCurrentClockChanged();
-
- mExecutor.runAllReady();
- assertEquals(View.INVISIBLE, mFakeDateView.getVisibility());
- }
-
- @Test
- public void testGetClock_nullClock_returnsNull() {
- when(mClockEventController.getClock()).thenReturn(null);
- assertNull(mController.getClock());
- }
-
- private void verifyAttachment(VerificationMode times) {
- verify(mClockRegistry, times).registerClockChangeListener(
- any(ClockRegistry.ClockChangeListener.class));
- verify(mClockEventController, times).registerListeners(mView);
- }
-
- @Test
- public void testSplitShadeEnabledSetToSmartspaceController() {
- mController.setSplitShadeEnabled(true);
- verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true);
- verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false);
- }
-
- @Test
- public void testSplitShadeDisabledSetToSmartspaceController() {
- mController.setSplitShadeEnabled(false);
- verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false);
- verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true);
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
deleted file mode 100644
index 4ed5fd0a6e71..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard;
-
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
-import static com.android.keyguard.KeyguardClockSwitch.LARGE;
-import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.TestCase.assertEquals;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.platform.test.annotations.DisableFlags;
-import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.clocks.ClockController;
-import com.android.systemui.plugins.clocks.ClockFaceController;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.StatusBarState;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-// Need to run on the main thread because KeyguardSliceView$Row init checks for
-// the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clock_switch layout is inflated.
-@RunWithLooper(setAsMainLooper = true)
-@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-public class KeyguardClockSwitchTest extends SysuiTestCase {
- @Mock
- ViewGroup mMockKeyguardSliceView;
-
- @Mock
- ClockController mClock;
-
- @Mock
- ClockFaceController mSmallClock;
-
- @Mock
- ClockFaceController mLargeClock;
-
- private FrameLayout mSmallClockFrame;
- private FrameLayout mLargeClockFrame;
- private KeyguardStatusAreaView mStatusArea;
-
- KeyguardClockSwitch mKeyguardClockSwitch;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
- when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
- .thenReturn(mMockKeyguardSliceView);
-
- when(mClock.getSmallClock()).thenReturn(mSmallClock);
- when(mClock.getLargeClock()).thenReturn(mLargeClock);
-
- when(mSmallClock.getView()).thenReturn(new TextView(getContext()));
- when(mLargeClock.getView()).thenReturn(new TextView(getContext()));
-
- LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
-
- @Override
- public View onCreateView(View parent, String name, Context context,
- AttributeSet attrs) {
- return onCreateView(name, context, attrs);
- }
-
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- if ("com.android.keyguard.KeyguardSliceView".equals(name)) {
- return mMockKeyguardSliceView;
- }
- return null;
- }
- });
- mKeyguardClockSwitch =
- (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
- mSmallClockFrame = mKeyguardClockSwitch
- .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view);
- mLargeClockFrame = mKeyguardClockSwitch
- .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large);
- mStatusArea = mKeyguardClockSwitch.findViewById(R.id.keyguard_status_area);
- mKeyguardClockSwitch.mChildrenAreLaidOut = true;
- }
-
- @Test
- public void noPluginConnected_showNothing() {
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 0);
- assertEquals(mSmallClockFrame.getChildCount(), 0);
- }
-
- @Test
- public void pluginConnectedThenDisconnected_showNothing() {
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 1);
- assertEquals(mSmallClockFrame.getChildCount(), 1);
-
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 0);
- assertEquals(mSmallClockFrame.getChildCount(), 0);
- }
-
- @Test
- public void onPluginConnected_showClock() {
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
-
- assertEquals(mClock.getSmallClock().getView().getParent(), mSmallClockFrame);
- assertEquals(mClock.getLargeClock().getView().getParent(), mLargeClockFrame);
- }
-
- @Test
- public void onPluginConnected_showSecondPluginClock() {
- // GIVEN a plugin has already connected
- ClockController otherClock = mock(ClockController.class);
- ClockFaceController smallClock = mock(ClockFaceController.class);
- ClockFaceController largeClock = mock(ClockFaceController.class);
- when(otherClock.getSmallClock()).thenReturn(smallClock);
- when(otherClock.getLargeClock()).thenReturn(largeClock);
- when(smallClock.getView()).thenReturn(new TextView(getContext()));
- when(largeClock.getView()).thenReturn(new TextView(getContext()));
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
-
- // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(otherClock.getSmallClock().getView().getParent()).isEqualTo(mSmallClockFrame);
- assertThat(otherClock.getLargeClock().getView().getParent()).isEqualTo(mLargeClockFrame);
- assertThat(mClock.getSmallClock().getView().getParent()).isNull();
- assertThat(mClock.getLargeClock().getView().getParent()).isNull();
- }
-
- @Test
- public void onPluginDisconnected_secondOfTwoDisconnected() {
- // GIVEN two plugins are connected
- ClockController otherClock = mock(ClockController.class);
- ClockFaceController smallClock = mock(ClockFaceController.class);
- ClockFaceController largeClock = mock(ClockFaceController.class);
- when(otherClock.getSmallClock()).thenReturn(smallClock);
- when(otherClock.getLargeClock()).thenReturn(largeClock);
- when(smallClock.getView()).thenReturn(new TextView(getContext()));
- when(largeClock.getView()).thenReturn(new TextView(getContext()));
- mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- // WHEN the second plugin is disconnected
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- // THEN nothing should be shown
- assertThat(otherClock.getSmallClock().getView().getParent()).isNull();
- assertThat(otherClock.getLargeClock().getView().getParent()).isNull();
- assertThat(mClock.getSmallClock().getView().getParent()).isNull();
- assertThat(mClock.getLargeClock().getView().getParent()).isNull();
- }
-
- @Test
- public void switchingToBigClockWithAnimation_makesSmallClockDisappear() {
- mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true);
-
- mKeyguardClockSwitch.mClockInAnim.end();
- mKeyguardClockSwitch.mClockOutAnim.end();
- mKeyguardClockSwitch.mStatusAreaAnim.end();
-
- assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
- }
-
- @Test
- public void switchingToBigClockNoAnimation_makesSmallClockDisappear() {
- mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false);
-
- assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
- }
-
- @Test
- public void switchingToSmallClockWithAnimation_makesBigClockDisappear() {
- mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true);
-
- mKeyguardClockSwitch.mClockInAnim.end();
- mKeyguardClockSwitch.mClockOutAnim.end();
- mKeyguardClockSwitch.mStatusAreaAnim.end();
-
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
- // only big clock is removed at switch
- assertThat(mLargeClockFrame.getParent()).isNull();
- assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
- }
-
- @Test
- public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
- mKeyguardClockSwitch.switchToClock(SMALL, false);
-
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
- // only big clock is removed at switch
- assertThat(mLargeClockFrame.getParent()).isNull();
- assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
- }
-
- @Test
- public void switchingToSmallClockAnimation_resetsStatusArea() {
- mKeyguardClockSwitch.switchToClock(SMALL, true);
-
- mKeyguardClockSwitch.mClockInAnim.end();
- mKeyguardClockSwitch.mClockOutAnim.end();
- mKeyguardClockSwitch.mStatusAreaAnim.end();
-
- assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
- assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
- assertThat(mStatusArea.getScaleX()).isEqualTo(1);
- assertThat(mStatusArea.getScaleY()).isEqualTo(1);
- }
-
- @Test
- public void switchingToSmallClockNoAnimation_resetsStatusArea() {
- mKeyguardClockSwitch.switchToClock(SMALL, false);
-
- assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
- assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
- assertThat(mStatusArea.getScaleX()).isEqualTo(1);
- assertThat(mStatusArea.getScaleY()).isEqualTo(1);
- }
-
-
- @Test
- public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
- assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
- assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index e5f0d7c6bf37..68f4acde7609 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -494,6 +495,7 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase()
// Start dreaming.
updateDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
// Hub times out immediately.
assertThat(scene).isEqualTo(CommunalScenes.Blank)
@@ -650,6 +652,7 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase()
// Start dreaming.
updateDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
// Hub times out immediately.
assertThat(scene).isEqualTo(Scenes.Dream)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 21019875f51e..9d5bf4dbdc3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -55,7 +55,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -887,7 +886,6 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
@Test
@EnableSceneContainer
- @Ignore("b/378766637")
fun lockscreenVisibilityWithScenes() =
testScope.runTest {
val isDeviceUnlocked by
@@ -896,6 +894,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
)
assertThat(isDeviceUnlocked).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 9edd62a8a784..6a2aae175d80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -16,21 +16,23 @@
package com.android.systemui.media.controls.ui.viewmodel
-import android.R
import android.content.packageManager
import android.content.pm.ApplicationInfo
import android.media.MediaMetadata
import android.media.session.MediaSession
import android.media.session.PlaybackState
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.mediaInstanceId
+import com.android.systemui.res.R
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -132,6 +134,31 @@ class MediaControlViewModelTest : SysuiTestCase() {
assertThat(underTest.setPlayer(playerModel!!)).isTrue()
}
+ @Test
+ fun reservedButtons_showScrubbingTimes() =
+ testScope.runTest {
+ val playerModel by collectLastValue(underTest.player)
+ val mediaData =
+ initMediaData(ARTIST, TITLE)
+ .copy(semanticActions = MediaButton(reserveNext = true, reservePrev = true))
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel?.actionButtons).isNotNull()
+ assertThat(playerModel!!.useSemanticActions).isTrue()
+ assertThat(playerModel!!.canShowTime).isTrue()
+
+ val buttons = playerModel!!.actionButtons
+
+ val prevButton = buttons.find { it.buttonId == R.id.actionPrev }!!
+ assertThat(prevButton.notVisibleValue).isEqualTo(ConstraintSet.GONE)
+ assertThat(prevButton.isVisibleWhenScrubbing).isEqualTo(false)
+
+ val nextButton = buttons.find { it.buttonId == R.id.actionNext }!!
+ assertThat(nextButton.notVisibleValue).isEqualTo(ConstraintSet.GONE)
+ assertThat(nextButton.isVisibleWhenScrubbing).isEqualTo(false)
+ }
+
private fun initMediaData(artist: String, title: String): MediaData {
val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 79bb0c401e78..06dd046564df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -74,6 +74,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.dozeInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -1270,8 +1271,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
authenticationMethod = AuthenticationMethodModel.None,
isLockscreenEnabled = false,
)
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ powerInteractor.setAsleepForTest()
underTest.start()
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+
powerInteractor.setAwakeForTest()
runCurrent()
@@ -2139,6 +2143,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val transitionStateFlow = prepareState()
underTest.start()
+ runCurrent()
emulateSceneTransition(transitionStateFlow, toScene = Scenes.Bouncer)
assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
@@ -2153,6 +2158,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val transitionStateFlow = prepareState()
underTest.start()
+ runCurrent()
emulateSceneTransition(transitionStateFlow, toScene = Scenes.Bouncer)
assertThat(currentScene).isEqualTo(Scenes.Bouncer)
@@ -2269,6 +2275,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
underTest.start()
+ runCurrent()
sceneInteractor.changeScene(Scenes.Shade, "")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
@@ -2350,6 +2357,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
prepareState()
underTest.start()
+ runCurrent()
// run all pending dismiss succeeded/cancelled calls from setup:
kosmos.fakeExecutor.runAllReady()
@@ -2461,13 +2469,18 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun switchFromDreamToLockscreen_whenLockedAndDreamStopped() =
testScope.runTest {
- keyguardInteractor.setDreaming(true)
val currentScene by collectLastValue(sceneInteractor.currentScene)
prepareState(initialSceneKey = Scenes.Dream)
- assertThat(currentScene).isEqualTo(Scenes.Dream)
underTest.start()
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ runCurrent()
+ keyguardInteractor.setDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Dream)
keyguardInteractor.setDreaming(false)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
}
@@ -2475,13 +2488,18 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun switchFromDreamToGone_whenUnlockedAndDreamStopped() =
testScope.runTest {
- keyguardInteractor.setDreaming(true)
val currentScene by collectLastValue(sceneInteractor.currentScene)
prepareState(initialSceneKey = Scenes.Dream, isDeviceUnlocked = true)
- assertThat(currentScene).isEqualTo(Scenes.Dream)
underTest.start()
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ runCurrent()
+ keyguardInteractor.setDreaming(true)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Dream)
keyguardInteractor.setDreaming(false)
+ advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Gone)
}
@@ -2684,6 +2702,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ runCurrent()
sceneInteractor.changeScene(Scenes.Shade, "reason")
sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
assertThat(currentScene).isEqualTo(Scenes.Shade)
@@ -2835,8 +2854,15 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
sceneInteractor.setTransitionState(transitionStateFlow)
initialSceneKey?.let {
+ if (isDeviceUnlocked && initialSceneKey != Scenes.Gone) {
+ // Pass through the Gone scene to populate device entry state properly.
+ transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
+ sceneInteractor.changeScene(Scenes.Gone, "prepareState, passing through Gone scene")
+ runCurrent()
+ }
+
transitionStateFlow.value = ObservableTransitionState.Idle(it)
- sceneInteractor.changeScene(it, "reason")
+ sceneInteractor.changeScene(it, "prepareState, initialSceneKey isn't null")
}
if (startsAwake) {
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 5b0b59de47c2..4a3be4487290 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -31,6 +31,10 @@ import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -280,8 +284,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mBackgroundExecutor.runAllReady();
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
}
@Test
@@ -357,7 +363,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
}
@Test
@@ -368,7 +375,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
}
@Test
@@ -385,7 +393,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
.setChannel(channel)
.setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
// THEN the notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
}
@Test
@@ -399,7 +408,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
.setChannel(null)
.setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
// THEN the notification is not redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
}
@Test
@@ -410,7 +420,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mWorkProfileNotif));
assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
}
@@ -422,7 +433,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mWorkProfileNotif));
assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
}
@@ -440,11 +452,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification doesn't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mWorkProfileNotif));
// THEN the current user and secondary user notifications do need to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
}
@Test
@@ -461,11 +476,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification needs to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mWorkProfileNotif));
// THEN the current user and secondary user notifications don't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
}
@Test
@@ -481,18 +499,20 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// THEN the secondary profile notification still needs to be redacted because the current
// user's setting takes precedence
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
}
@Test
public void testHasSensitiveContent_redacted() {
// Allow private notifications for this user
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// Sensitive Content notifications are always redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mSensitiveContentNotif));
+ assertEquals(REDACTION_TYPE_SENSITIVE_CONTENT,
+ mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
}
@Test
@@ -707,9 +727,11 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
.putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
// it's a global field, confirm secondary too
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
mSecondaryUser.id));
@@ -732,7 +754,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mCurrentUserNotif));
verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@@ -763,7 +786,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
+ assertEquals(REDACTION_TYPE_PUBLIC, mLockscreenUserManager.getRedactionType(notifEntry));
}
@Test
@@ -784,7 +807,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertEquals(REDACTION_TYPE_PUBLIC,
+ mLockscreenUserManager.getRedactionType(mSecondaryUserNotif));
verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 9e7befd3cf92..8f21ddff524c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -50,6 +50,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -840,7 +842,13 @@ class SensitiveContentCoordinatorTest(flags: FlagsParameterization) : SysuiTestC
whenever(sbn).thenReturn(mockSbn)
whenever(row).thenReturn(mockRow)
}
- whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
+ val redactionType =
+ if (needsRedaction) {
+ REDACTION_TYPE_PUBLIC
+ } else {
+ REDACTION_TYPE_NONE
+ }
+ whenever(lockscreenUserManager.getRedactionType(mockEntry)).thenReturn(redactionType)
whenever(mockEntry.rowExists()).thenReturn(true)
return object : ListEntry("key", 0) {
override fun getRepresentativeEntry(): NotificationEntry = mockEntry
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 2f77b33c96cb..3c772fdbe0b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -27,6 +27,8 @@ import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDIN
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
@@ -42,6 +44,7 @@ import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Before
@@ -212,12 +215,12 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
.thenReturn(false)
val oldAdjustment: NotifUiAdjustment = adjustmentProvider.calculateAdjustment(entry)
- assertFalse(oldAdjustment.needsRedaction)
+ assertEquals(REDACTION_TYPE_NONE, oldAdjustment.redactionType)
whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
.thenReturn(true)
val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
- assertTrue(newAdjustment.needsRedaction)
+ assertEquals(REDACTION_TYPE_PUBLIC, newAdjustment.redactionType)
// Then: need re-inflation
assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
@@ -229,12 +232,12 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
.thenReturn(false)
val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
- assertFalse(oldAdjustment.needsRedaction)
+ assertEquals(REDACTION_TYPE_NONE, oldAdjustment.redactionType)
whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
.thenReturn(true)
val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
- assertFalse(newAdjustment.needsRedaction)
+ assertEquals(REDACTION_TYPE_NONE, newAdjustment.redactionType)
// Then: need no re-inflation
assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index 98bf0e6170d4..739a9c956178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -45,7 +45,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 79b5cc37119f..0652a835cb7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
@@ -120,6 +121,22 @@ public class SystemUIDialogTest extends SysuiTestCase {
}
@Test
+ public void testRegisterReceiverWithoutAcsd() {
+ SystemUIDialog dialog = createDialogWithDelegate(mContext, mDelegate,
+ false /* shouldAcsdDismissDialog */);
+ final ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ final ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+
+ dialog.show();
+ verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(),
+ intentFilterCaptor.capture(), ArgumentMatchers.eq(null), ArgumentMatchers.any());
+ assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
+ assertFalse(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS)
public void usePredictiveBackAnimFlag() {
final SystemUIDialog dialog = new SystemUIDialog(mContext);
@@ -163,7 +180,8 @@ public class SystemUIDialogTest extends SysuiTestCase {
public void delegateIsCalled_inCorrectOrder() {
Configuration configuration = new Configuration();
InOrder inOrder = Mockito.inOrder(mDelegate);
- SystemUIDialog dialog = createDialogWithDelegate();
+ SystemUIDialog dialog = createDialogWithDelegate(mContext, mDelegate,
+ true /* shouldAcsdDismissDialog */);
dialog.show();
dialog.onWindowFocusChanged(/* hasFocus= */ true);
@@ -178,7 +196,8 @@ public class SystemUIDialogTest extends SysuiTestCase {
inOrder.verify(mDelegate).onStop(dialog);
}
- private SystemUIDialog createDialogWithDelegate() {
+ private SystemUIDialog createDialogWithDelegate(Context context,
+ SystemUIDialog.Delegate delegate, boolean shouldAcsdDismissDialog) {
SystemUIDialog.Factory factory = new SystemUIDialog.Factory(
getContext(),
Dependency.get(SystemUIDialogManager.class),
@@ -186,6 +205,6 @@ public class SystemUIDialogTest extends SysuiTestCase {
Dependency.get(BroadcastDispatcher.class),
Dependency.get(DialogTransitionAnimator.class)
);
- return factory.create(mDelegate);
+ return factory.create(delegate, context, shouldAcsdDismissDialog);
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt
index c90183df9847..1cc55bf87b1a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt
@@ -127,12 +127,6 @@ class KeyguardBypassInteractorTest : SysuiTestCase() {
kosmos.configureKeyguardBypass(isBypassAvailable = skipIsBypassAvailableCheck)
underTest = kosmos.keyguardBypassInteractor
- // bouncerShowing false, !onLockscreenScene false
- // !onLockscreenScene false
- setScene(
- bouncerShowing = !skipBouncerShowingCheck,
- onLockscreenScene = skipOnLockscreenSceneCheck,
- )
// alternateBouncerShowing false
setAlternateBouncerShowing(!skipAlternateBouncerShowingCheck)
// launchingAffordance false
@@ -141,6 +135,13 @@ class KeyguardBypassInteractorTest : SysuiTestCase() {
setPulseExpanding(!skipPulseExpandingCheck)
// qsExpanding false
setQsExpanded(!skipQsExpandedCheck)
+
+ // bouncerShowing false, !onLockscreenScene false
+ // !onLockscreenScene false
+ setScene(
+ bouncerShowing = !skipBouncerShowingCheck,
+ onLockscreenScene = skipOnLockscreenSceneCheck,
+ )
}
private fun setAlternateBouncerShowing(alternateBouncerVisible: Boolean) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index b19645fadbdf..8fb95e843ec1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -40,9 +41,14 @@ import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -51,6 +57,11 @@ class OngoingCallInteractorTest : SysuiTestCase() {
private val repository = kosmos.activeNotificationListRepository
private val underTest = kosmos.ongoingCallInteractor
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
@Test
fun noNotification_emitsNoCall() = runTest {
val state by collectLastValue(underTest.ongoingCallState)
@@ -210,8 +221,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
@Test
fun ongoingCallNotification_setsRequiresStatusBarVisibleTrue() =
kosmos.runTest {
- val ongoingCallState by collectLastValue(underTest.ongoingCallState)
-
+ val isStatusBarRequired by collectLastValue(underTest.isStatusBarRequiredForOngoingCall)
val requiresStatusBarVisibleInRepository by
collectLastValue(
kosmos.fakeStatusBarModeRepository.defaultDisplay
@@ -222,21 +232,9 @@ class OngoingCallInteractorTest : SysuiTestCase() {
kosmos.fakeStatusBarWindowControllerStore.defaultDisplay
.ongoingProcessRequiresStatusBarVisible
)
- repository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply {
- addIndividualNotif(
- activeNotificationModel(
- key = "notif1",
- whenTime = 1000L,
- callType = CallType.Ongoing,
- uid = UID,
- )
- )
- }
- .build()
+ postOngoingCallNotification()
- assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat(isStatusBarRequired).isTrue()
assertThat(requiresStatusBarVisibleInRepository).isTrue()
assertThat(requiresStatusBarVisibleInWindowController).isTrue()
}
@@ -244,8 +242,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
@Test
fun notificationRemoved_setsRequiresStatusBarVisibleFalse() =
kosmos.runTest {
- val ongoingCallState by collectLastValue(underTest.ongoingCallState)
-
+ val isStatusBarRequired by collectLastValue(underTest.isStatusBarRequiredForOngoingCall)
val requiresStatusBarVisibleInRepository by
collectLastValue(
kosmos.fakeStatusBarModeRepository.defaultDisplay
@@ -257,23 +254,11 @@ class OngoingCallInteractorTest : SysuiTestCase() {
.ongoingProcessRequiresStatusBarVisible
)
- repository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply {
- addIndividualNotif(
- activeNotificationModel(
- key = "notif1",
- whenTime = 1000L,
- callType = CallType.Ongoing,
- uid = UID,
- )
- )
- }
- .build()
+ postOngoingCallNotification()
repository.activeNotifications.value = ActiveNotificationsStore()
- assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.NoCall::class.java)
+ assertThat(isStatusBarRequired).isFalse()
assertThat(requiresStatusBarVisibleInRepository).isFalse()
assertThat(requiresStatusBarVisibleInWindowController).isFalse()
}
@@ -295,19 +280,8 @@ class OngoingCallInteractorTest : SysuiTestCase() {
)
kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
- repository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply {
- addIndividualNotif(
- activeNotificationModel(
- key = "notif1",
- whenTime = 1000L,
- callType = CallType.Ongoing,
- uid = UID,
- )
- )
- }
- .build()
+
+ postOngoingCallNotification()
assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
assertThat(requiresStatusBarVisibleInRepository).isTrue()
@@ -321,6 +295,99 @@ class OngoingCallInteractorTest : SysuiTestCase() {
assertThat(requiresStatusBarVisibleInWindowController).isFalse()
}
+ @Test
+ fun gestureHandler_inCall_notFullscreen_doesNotListen() =
+ kosmos.runTest {
+ val ongoingCallState by collectLastValue(underTest.ongoingCallState)
+
+ clearInvocations(kosmos.swipeStatusBarAwayGestureHandler)
+ // Set up notification but not in fullscreen
+ kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
+ postOngoingCallNotification()
+
+ assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ verify(kosmos.swipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun gestureHandler_inCall_fullscreen_addsListener() =
+ kosmos.runTest {
+ val isGestureListeningEnabled by collectLastValue(underTest.isGestureListeningEnabled)
+
+ // Set up notification and fullscreen mode
+ kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ postOngoingCallNotification()
+
+ assertThat(isGestureListeningEnabled).isTrue()
+ verify(kosmos.swipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun gestureHandler_inCall_fullscreen_chipSwiped_removesListener() =
+ kosmos.runTest {
+ val swipeAwayState by collectLastValue(underTest.isChipSwipedAway)
+
+ // Set up notification and fullscreen mode
+ kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
+ postOngoingCallNotification()
+
+ clearInvocations(kosmos.swipeStatusBarAwayGestureHandler)
+
+ underTest.onStatusBarSwiped()
+
+ assertThat(swipeAwayState).isTrue()
+ verify(kosmos.swipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(any())
+ }
+
+ @Test
+ fun chipSwipedAway_setsRequiresStatusBarVisibleFalse() =
+ kosmos.runTest {
+ val isStatusBarRequiredForOngoingCall by
+ collectLastValue(underTest.isStatusBarRequiredForOngoingCall)
+ val requiresStatusBarVisibleInRepository by
+ collectLastValue(
+ kosmos.fakeStatusBarModeRepository.defaultDisplay
+ .ongoingProcessRequiresStatusBarVisible
+ )
+ val requiresStatusBarVisibleInWindowController by
+ collectLastValue(
+ kosmos.fakeStatusBarWindowControllerStore.defaultDisplay
+ .ongoingProcessRequiresStatusBarVisible
+ )
+
+ // Start with an ongoing call (which should set status bar required)
+ postOngoingCallNotification()
+
+ assertThat(isStatusBarRequiredForOngoingCall).isTrue()
+ assertThat(requiresStatusBarVisibleInRepository).isTrue()
+ assertThat(requiresStatusBarVisibleInWindowController).isTrue()
+
+ // Swipe away the chip
+ underTest.onStatusBarSwiped()
+
+ // Verify status bar is no longer required
+ assertThat(requiresStatusBarVisibleInRepository).isFalse()
+ assertThat(requiresStatusBarVisibleInWindowController).isFalse()
+ }
+
+ private fun postOngoingCallNotification() {
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = 1000L,
+ callType = CallType.Ongoing,
+ uid = UID,
+ )
+ )
+ }
+ .build()
+ }
+
companion object {
private const val UID = 885
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
new file mode 100644
index 000000000000..2ad1124d72d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.touchpad.tutorial.ui
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
+import com.android.systemui.touchpad.tutorial.ui.composable.toTutorialActionState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StateTransitionsTest : SysuiTestCase() {
+
+ companion object {
+ private const val START_MARKER = "startMarker"
+ private const val END_MARKER = "endMarker"
+ private const val SUCCESS_ANIMATION = 0
+ }
+
+ // needed to simulate caching last state as it's used to create new state
+ private var lastState: TutorialActionState = NotStarted
+
+ private fun GestureState.toTutorialActionState(): TutorialActionState {
+ val newState =
+ this.toGestureUiState(
+ progressStartMarker = START_MARKER,
+ progressEndMarker = END_MARKER,
+ successAnimation = SUCCESS_ANIMATION,
+ )
+ .toTutorialActionState(lastState)
+ lastState = newState
+ return lastState
+ }
+
+ @Test
+ fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() {
+ val happyPath =
+ listOf(
+ GestureState.NotStarted,
+ GestureState.InProgress(0f),
+ GestureState.InProgress(0.5f),
+ GestureState.InProgress(1f),
+ GestureState.Finished,
+ )
+
+ val resultingStates = mutableListOf<TutorialActionState>()
+ happyPath.forEach { resultingStates.add(it.toTutorialActionState()) }
+
+ assertThat(resultingStates)
+ .containsExactly(
+ NotStarted,
+ InProgress(0f, START_MARKER, END_MARKER),
+ InProgress(0.5f, START_MARKER, END_MARKER),
+ InProgress(1f, START_MARKER, END_MARKER),
+ Finished(SUCCESS_ANIMATION),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() {
+ val errorPath =
+ listOf(
+ GestureState.NotStarted,
+ GestureState.InProgress(0f),
+ GestureState.Error,
+ GestureState.InProgress(0.5f),
+ GestureState.InProgress(1f),
+ GestureState.Finished,
+ )
+
+ val resultingStates = mutableListOf<TutorialActionState>()
+ errorPath.forEach { resultingStates.add(it.toTutorialActionState()) }
+
+ assertThat(resultingStates)
+ .containsExactly(
+ NotStarted,
+ InProgress(0f, START_MARKER, END_MARKER),
+ Error,
+ InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)),
+ InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)),
+ Finished(SUCCESS_ANIMATION),
+ )
+ .inOrder()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
index 8d0d172ada12..e23348be1590 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Error
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
@@ -56,9 +57,9 @@ class HomeGestureRecognizerTest : SysuiTestCase() {
}
@Test
- fun doesntTriggerGestureFinished_onGestureSpeedTooSlow() {
+ fun triggersError_onGestureSpeedTooSlow() {
velocityTracker.setVelocity(Velocity(SLOW))
- assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Error)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
index 7a77b63a8925..2fe37aef7e0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Error
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
@@ -57,9 +58,9 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() {
}
@Test
- fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
+ fun triggersError_onGestureSpeedTooHigh() {
velocityTracker.setVelocity(Velocity(FAST))
- assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Error)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
index de410894d0c0..533665eaa626 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Error
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
@@ -64,12 +65,12 @@ class ThreeFingerGestureRecognizerTest(
}
@Test
- fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
- assertStateAfterEvents(events = tooShortGesture, expectedState = NotStarted)
+ fun triggersGestureError_onGestureDistanceTooShort() {
+ assertStateAfterEvents(events = tooShortGesture, expectedState = Error)
}
@Test
- fun doesntTriggerGestureFinished_onThreeFingersSwipeInOtherDirections() {
+ fun triggersGestureError_onThreeFingersSwipeInOtherDirections() {
val allThreeFingerGestures =
listOf(
ThreeFingerGesture.swipeUp(),
@@ -78,7 +79,7 @@ class ThreeFingerGestureRecognizerTest(
ThreeFingerGesture.swipeRight(),
)
val invalidGestures = allThreeFingerGestures.filter { it.differentFromAnyOf(validGestures) }
- invalidGestures.forEach { assertStateAfterEvents(events = it, expectedState = NotStarted) }
+ invalidGestures.forEach { assertStateAfterEvents(events = it, expectedState = Error) }
}
@Test
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f927e26e0cd9..56aaf4c0c564 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3932,6 +3932,8 @@
<string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_done_button">Done</string>
+ <!-- Screen title after gesture was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="gesture_error_title">Try again!</string>
<!-- BACK GESTURE -->
<!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_action_title">Go back</string>
@@ -3941,6 +3943,8 @@
<string name="touchpad_back_gesture_success_title">Nice!</string>
<!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
+ <!-- Text shown to the user after back gesture was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="touchpad_back_gesture_error_body">To go back using your touchpad, swipe left or right using three fingers</string>
<!-- HOME GESTURE -->
<!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_home_gesture_action_title">Go home</string>
@@ -3950,6 +3954,8 @@
<string name="touchpad_home_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_home_gesture_success_body">You completed the go home gesture</string>
+ <!-- Text shown to the user after home gesture was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="touchpad_home_gesture_error_body">Swipe up with three fingers on your touchpad to go to your home screen</string>
<!-- RECENT APPS GESTURE -->
<!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_action_title">View recent apps</string>
@@ -3959,6 +3965,8 @@
<string name="touchpad_recent_apps_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string>
+ <!-- Text shown to the user after recent gesture was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="touchpad_recent_gesture_error_body">To view recent apps, swipe up and hold using three fingers on your touchpad</string>
<!-- KEYBOARD TUTORIAL-->
<!-- Action key tutorial title [CHAR LIMIT=NONE] -->
@@ -3969,6 +3977,9 @@
<string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
<string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
+ <!-- Text shown to the user after action key tutorial was not done correctly [CHAR LIMIT=NONE] -->
+ <string name="touchpad_action_key_error_body">Press the action key on your keyboard to view all of your apps</string>
+
<!-- Content description for the animation playing during the tutorial. The user can click the animation to pause and unpause playback. [CHAR LIMIT=NONE] -->
<string name="tutorial_animation_content_description">Tutorial animation, click to pause and resume play.</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ec0f582dcb47..0e1eccc8231c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -55,7 +55,6 @@ import com.android.systemui.shared.regionsampling.RegionSampler;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.util.ViewController;
@@ -85,7 +84,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
- private final NotificationIconContainerAlwaysOnDisplayViewBinder mNicViewBinder;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -147,7 +145,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
ClockRegistry clockRegistry,
KeyguardSliceViewController keyguardSliceViewController,
LockscreenSmartspaceController smartspaceController,
- NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main DelayableExecutor uiExecutor,
@@ -163,7 +160,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
mSmartspaceController = smartspaceController;
- mNicViewBinder = nicViewBinder;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
@@ -277,7 +273,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
hideSliceViewAndNotificationIconContainer();
return;
}
- updateAodIcons();
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
mBgExecutor.execute(() -> {
@@ -569,21 +564,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
- private void updateAodIcons() {
- if (!MigrateClocksToBlueprint.isEnabled()) {
- NotificationIconContainer nic = (NotificationIconContainer)
- mView.findViewById(
- com.android.systemui.res.R.id.left_aligned_notification_icon_container);
- if (mAodIconsBindHandle != null) {
- mAodIconsBindHandle.dispose();
- }
- if (nic != null) {
- mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
- mAodIconContainer = nic;
- }
- }
- }
-
private void setClock(ClockController clock) {
if (MigrateClocksToBlueprint.isEnabled()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/FontStyles.kt b/packages/SystemUI/src/com/android/systemui/FontStyles.kt
new file mode 100644
index 000000000000..d8cd6c87a1ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/FontStyles.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+/** String tokens for the different GSF font families. */
+object FontStyles {
+
+ const val GSF_LABEL_MEDIUM = "gsf-label-medium"
+ const val GSF_LABEL_LARGE = "gsf-label-large"
+
+ const val GSF_BODY_MEDIUM = "gsf-body-medium"
+
+ const val GSF_TITLE_SMALL_EMPHASIZED = "gsf-title-small-emphasized"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 1176cb0523c1..c17055740166 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -52,6 +52,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.systemui.DualToneHandler;
+import com.android.systemui.FontStyles;
import com.android.systemui.battery.unified.BatteryColors;
import com.android.systemui.battery.unified.BatteryDrawableState;
import com.android.systemui.battery.unified.BatteryLayersDrawable;
@@ -387,7 +388,8 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
if (gsfQuickSettings()) {
- mBatteryPercentView.setTypeface(Typeface.create("gsf-label-large", Typeface.NORMAL));
+ mBatteryPercentView.setTypeface(
+ Typeface.create(FontStyles.GSF_LABEL_LARGE, Typeface.NORMAL));
}
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
addView(mBatteryPercentView, new LayoutParams(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 5644e6b3b9bf..34679b08cf20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -183,7 +183,12 @@ constructor(
this@CommunalSceneStartable.isDreaming = isDreaming
if (scene.isCommunal() && isDreaming && timeoutJob == null) {
// If dreaming starts after timeout has expired, ex. if dream restarts under
- // the hub, just close the hub immediately.
+ // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub. The
+ // delay is necessary so the KeyguardInteractor.isAbleToDream flow passes
+ // through that same amount of delay and publishes a new value which is then
+ // picked up by the HomeSceneFamilyResolver such that the next call to
+ // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream".
+ delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS)
communalSceneInteractor.changeScene(
CommunalScenes.Blank,
"dream started after timeout",
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 1fc549469b55..3050cba12f09 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -61,6 +61,7 @@ import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.settings.MultiUserUtilsModule;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.settings.brightness.dagger.BrightnessSliderModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.startable.Dependencies;
@@ -124,6 +125,7 @@ import javax.inject.Named;
AccessibilityRepositoryModule.class,
AospPolicyModule.class,
BatterySaverModule.class,
+ BrightnessSliderModule.class,
CentralSurfacesModule.class,
ClipboardOverlayOverrideModule.class,
CollapsedStatusBarFragmentStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 058e5874ae7c..950a727aedae 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -79,6 +79,9 @@ private fun buildScreenConfig() =
bodyResId = R.string.tutorial_action_key_guidance,
titleSuccessResId = R.string.tutorial_action_key_success_title,
bodySuccessResId = R.string.tutorial_action_key_success_body,
+ // error state for action key is not implemented yet so below should never appear
+ titleErrorResId = R.string.gesture_error_title,
+ bodyErrorResId = R.string.touchpad_action_key_error_body,
),
animations = TutorialScreenConfig.Animations(educationResId = R.raw.action_key_edu),
)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 0c1bc835517a..c40adfe6baf8 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -45,18 +45,33 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
sealed interface TutorialActionState {
data object NotStarted : TutorialActionState
data class InProgress(
- val progress: Float = 0f,
- val startMarker: String? = null,
- val endMarker: String? = null,
- ) : TutorialActionState
+ override val progress: Float = 0f,
+ override val startMarker: String? = null,
+ override val endMarker: String? = null,
+ ) : TutorialActionState, Progress
data class Finished(@RawRes val successAnimation: Int) : TutorialActionState
+
+ data object Error : TutorialActionState
+
+ data class InProgressAfterError(val inProgress: InProgress) :
+ TutorialActionState, Progress by inProgress
+}
+
+interface Progress {
+ val progress: Float
+ val startMarker: String?
+ val endMarker: String?
}
@Composable
@@ -133,10 +148,13 @@ fun TutorialDescription(
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
val (titleTextId, bodyTextId) =
- if (actionState is Finished) {
- config.strings.titleSuccessResId to config.strings.bodySuccessResId
- } else {
- config.strings.titleResId to config.strings.bodyResId
+ when (actionState) {
+ is Finished -> config.strings.titleSuccessResId to config.strings.bodySuccessResId
+ Error,
+ is InProgressAfterError ->
+ config.strings.titleErrorResId to config.strings.bodyErrorResId
+ is NotStarted,
+ is InProgress -> config.strings.titleResId to config.strings.bodyResId
}
Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
Text(
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index ad18817704aa..b0816ce608a0 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -47,8 +47,10 @@ import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.LottieDynamicProperties
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
import com.android.systemui.res.R
@@ -72,16 +74,18 @@ fun TutorialAnimation(
},
) { state ->
when (state) {
- NotStarted::class ->
+ NotStarted::class,
+ Error::class ->
EducationAnimation(
config.animations.educationResId,
config.colors.animationColors,
)
- InProgress::class ->
+ InProgress::class,
+ InProgressAfterError::class ->
InProgressAnimation(
// actionState can be already of different class while this composable is
// transitioning to another one
- actionState as? InProgress,
+ actionState as? Progress,
config.animations.educationResId,
config.colors.animationColors,
)
@@ -138,14 +142,14 @@ private fun SuccessAnimation(
@Composable
private fun InProgressAnimation(
- state: InProgress?,
+ state: Progress?,
@RawRes inProgressAnimationId: Int,
animationProperties: LottieDynamicProperties,
) {
// Caching latest progress for when we're animating this view away and state is null.
// Without this there's jumpcut in the animation while it's animating away.
// state should never be null when composable appears, only when disappearing
- val cached = remember { Ref<InProgress>() }
+ val cached = remember { Ref<Progress>() }
cached.value = state ?: cached.value
val progress = cached.value?.progress ?: 0f
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
index 60dfed3a67a4..26259912741a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
@@ -38,6 +38,8 @@ data class TutorialScreenConfig(
@StringRes val bodyResId: Int,
@StringRes val titleSuccessResId: Int,
@StringRes val bodySuccessResId: Int,
+ @StringRes val titleErrorResId: Int,
+ @StringRes val bodyErrorResId: Int,
)
data class Animations(@RawRes val educationResId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 26c286df01d7..0d9474e07ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -200,7 +200,10 @@ constructor(
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
*
- * Allow a brief moment to prevent rapidly oscillating between true/false signals.
+ * Allow a brief moment to prevent rapidly oscillating between true/false signals. The amount of
+ * time is [IS_ABLE_TO_DREAM_DELAY_MS] - consumers should consider waiting for that long before
+ * examining the value of this flow, to let other consumers have enough time to also see that
+ * same new value.
*/
val isAbleToDream: Flow<Boolean> =
dozeTransitionModel
@@ -212,7 +215,7 @@ constructor(
// do not immediately process any dreaming information when exiting AOD. It
// should actually be quite strange to leave AOD and then go straight to
// DREAMING so this should be fine.
- delay(500L)
+ delay(IS_ABLE_TO_DREAM_DELAY_MS)
isDreaming
.sample(powerInteractor.isAwake) { isDreaming, isAwake ->
isDreaming && isAwake
@@ -550,5 +553,11 @@ constructor(
companion object {
private const val TAG = "KeyguardInteractor"
+ /**
+ * Amount of time that [KeyguardInteractor.isAbleToDream] is delayed; consumers of that flow
+ * should consider waiting this amount of time before check the value of this flow, to let
+ * other consumers have enough time to see the new value.
+ */
+ const val IS_ABLE_TO_DREAM_DELAY_MS = 500L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 273d763a8c57..0a958e9d06a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -27,7 +27,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
@@ -73,7 +72,6 @@ object KeyguardClockViewBinder {
// When changing to new clock, we need to remove old views from burnInLayer
var lastClock: ClockController? = null
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.currentClock.collect { currentClock ->
if (lastClock != currentClock) {
cleanupClockViews(
@@ -99,7 +97,6 @@ object KeyguardClockViewBinder {
}
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockSize.collect { clockSize ->
updateBurnInLayer(keyguardRootView, viewModel, clockSize)
blueprintInteractor.refreshBlueprint(Type.ClockSize)
@@ -107,7 +104,6 @@ object KeyguardClockViewBinder {
}
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockShouldBeCentered.collect {
viewModel.currentClock.value?.let {
// TODO(b/301502635): remove "!it.config.useCustomClockScene" when
@@ -125,7 +121,6 @@ object KeyguardClockViewBinder {
}
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
combine(
viewModel.hasAodIcons,
rootViewModel.isNotifIconContainerVisible.map { it.value },
@@ -143,7 +138,6 @@ object KeyguardClockViewBinder {
}
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
aodBurnInViewModel.movement.collect { burnInModel ->
viewModel.currentClock.value
?.largeClock
@@ -159,7 +153,6 @@ object KeyguardClockViewBinder {
}
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.largeClockTextSize.collect { fontSizePx ->
viewModel.currentClock.value
?.largeClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index f121aabe795a..cd4d9dcf366c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -31,14 +31,12 @@ import android.view.View.OnLayoutChangeListener
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
-import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
@@ -56,7 +54,6 @@ import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardViewMediator
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -90,12 +87,9 @@ import kotlin.math.min
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -161,21 +155,19 @@ object KeyguardRootViewBinder {
disposables +=
view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (MigrateClocksToBlueprint.isEnabled) {
- launch("$TAG#topClippingBounds") {
- val clipBounds = Rect()
- viewModel.topClippingBounds.collect { clipTop ->
- if (clipTop == null) {
- view.setClipBounds(null)
- } else {
- clipBounds.apply {
- top = clipTop
- left = view.getLeft()
- right = view.getRight()
- bottom = view.getBottom()
- }
- view.setClipBounds(clipBounds)
+ launch("$TAG#topClippingBounds") {
+ val clipBounds = Rect()
+ viewModel.topClippingBounds.collect { clipTop ->
+ if (clipTop == null) {
+ view.setClipBounds(null)
+ } else {
+ clipBounds.apply {
+ top = clipTop
+ left = view.getLeft()
+ right = view.getRight()
+ bottom = view.getBottom()
}
+ view.setClipBounds(clipBounds)
}
}
}
@@ -190,40 +182,36 @@ object KeyguardRootViewBinder {
}
}
- if (MigrateClocksToBlueprint.isEnabled) {
- launch("$TAG#translationY") {
- // When translation happens in burnInLayer, it won't be weather clock
- // large clock isn't added to burnInLayer due to its scale transition
- // so we also need to add translation to it here
- // same as translationX
- viewModel.translationY.collect { y ->
- childViews[burnInLayerId]?.translationY = y
- childViews[largeClockId]?.translationY = y
- childViews[aodNotificationIconContainerId]?.translationY = y
- }
+ launch("$TAG#translationY") {
+ // When translation happens in burnInLayer, it won't be weather clock large
+ // clock isn't added to burnInLayer due to its scale transition so we also
+ // need to add translation to it here same as translationX
+ viewModel.translationY.collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
}
+ }
- launch("$TAG#translationX") {
- viewModel.translationX.collect { state ->
- val px = state.value ?: return@collect
- when {
- state.isToOrFrom(KeyguardState.AOD) -> {
- // Large Clock is not translated in the x direction
- childViews[burnInLayerId]?.translationX = px
- childViews[aodNotificationIconContainerId]?.translationX =
- px
- }
- state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
- for ((key, childView) in childViews.entries) {
- when (key) {
- indicationArea,
- startButton,
- endButton,
- deviceEntryIcon -> {
- // Do not move these views
- }
- else -> childView.translationX = px
+ launch("$TAG#translationX") {
+ viewModel.translationX.collect { state ->
+ val px = state.value ?: return@collect
+ when {
+ state.isToOrFrom(KeyguardState.AOD) -> {
+ // Large Clock is not translated in the x direction
+ childViews[burnInLayerId]?.translationX = px
+ childViews[aodNotificationIconContainerId]?.translationX = px
+ }
+ state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+ for ((key, childView) in childViews.entries) {
+ when (key) {
+ indicationArea,
+ startButton,
+ endButton,
+ deviceEntryIcon -> {
+ // Do not move these views
}
+ else -> childView.translationX = px
}
}
}
@@ -263,95 +251,92 @@ object KeyguardRootViewBinder {
}
}
- if (MigrateClocksToBlueprint.isEnabled) {
- launch {
- viewModel.burnInLayerVisibility.collect { visibility ->
- childViews[burnInLayerId]?.visibility = visibility
- }
+ launch {
+ viewModel.burnInLayerVisibility.collect { visibility ->
+ childViews[burnInLayerId]?.visibility = visibility
}
+ }
- launch {
- viewModel.burnInLayerAlpha.collect { alpha ->
- childViews[statusViewId]?.alpha = alpha
- }
+ launch {
+ viewModel.burnInLayerAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
}
+ }
- launch {
- viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
- childViews[statusViewId]?.alpha = alpha
- }
+ launch {
+ viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
}
+ }
- launch {
- viewModel.scale.collect { scaleViewModel ->
- if (scaleViewModel.scaleClockOnly) {
- // For clocks except weather clock, we have scale transition
- // besides translate
- childViews[largeClockId]?.let {
- it.scaleX = scaleViewModel.scale
- it.scaleY = scaleViewModel.scale
- }
+ launch {
+ viewModel.scale.collect { scaleViewModel ->
+ if (scaleViewModel.scaleClockOnly) {
+ // For clocks except weather clock, we have scale transition besides
+ // translate
+ childViews[largeClockId]?.let {
+ it.scaleX = scaleViewModel.scale
+ it.scaleY = scaleViewModel.scale
}
}
}
+ }
- launch {
- blueprintViewModel.currentTransition.collect { currentTransition ->
- // When blueprint/clock transitions end (null), make sure NSSL is in
- // the right place
- if (currentTransition == null) {
- childViews[nsslPlaceholderId]?.let { notificationListPlaceholder
- ->
- viewModel.onNotificationContainerBoundsChanged(
- notificationListPlaceholder.top.toFloat(),
- notificationListPlaceholder.bottom.toFloat(),
- animate = true,
- )
- }
+ launch {
+ blueprintViewModel.currentTransition.collect { currentTransition ->
+ // When blueprint/clock transitions end (null), make sure NSSL is in the
+ // right place
+ if (currentTransition == null) {
+ childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
+ viewModel.onNotificationContainerBoundsChanged(
+ notificationListPlaceholder.top.toFloat(),
+ notificationListPlaceholder.bottom.toFloat(),
+ animate = true,
+ )
}
}
}
+ }
- launch {
- val iconsAppearTranslationPx =
- configuration
- .getDimensionPixelSize(R.dimen.shelf_appear_translation)
- .stateIn(this)
- viewModel.isNotifIconContainerVisible.collect { isVisible ->
- if (isVisible.value) {
- blueprintViewModel.refreshBlueprint()
- }
- childViews[aodNotificationIconContainerId]
- ?.setAodNotifIconContainerIsVisible(
- isVisible,
- iconsAppearTranslationPx.value,
- screenOffAnimationController,
- )
+ launch {
+ val iconsAppearTranslationPx =
+ configuration
+ .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ .stateIn(this)
+ viewModel.isNotifIconContainerVisible.collect { isVisible ->
+ if (isVisible.value) {
+ blueprintViewModel.refreshBlueprint()
}
+ childViews[aodNotificationIconContainerId]
+ ?.setAodNotifIconContainerIsVisible(
+ isVisible,
+ iconsAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
}
+ }
- interactionJankMonitor?.let { jankMonitor ->
- launch {
- viewModel.goneToAodTransition.collect {
- when (it.transitionState) {
- TransitionState.STARTED -> {
- val clockId = clockInteractor.renderedClockId
- val builder =
- InteractionJankMonitor.Configuration.Builder
- .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
- .setTag(clockId)
- jankMonitor.begin(builder)
- }
- TransitionState.CANCELED ->
- jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
- TransitionState.FINISHED -> {
- if (MigrateClocksToBlueprint.isEnabled) {
- keyguardViewMediator?.maybeHandlePendingLock()
- }
- jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
- }
- TransitionState.RUNNING -> Unit
+ interactionJankMonitor?.let { jankMonitor ->
+ launch {
+ viewModel.goneToAodTransition.collect {
+ when (it.transitionState) {
+ TransitionState.STARTED -> {
+ val clockId = clockInteractor.renderedClockId
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ CUJ_SCREEN_OFF_SHOW_AOD,
+ view,
+ )
+ .setTag(clockId)
+ jankMonitor.begin(builder)
+ }
+ TransitionState.CANCELED ->
+ jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.FINISHED -> {
+ keyguardViewMediator?.maybeHandlePendingLock()
+ jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
}
+ TransitionState.RUNNING -> Unit
}
}
}
@@ -406,13 +391,11 @@ object KeyguardRootViewBinder {
}
}
- if (MigrateClocksToBlueprint.isEnabled) {
- burnInParams.update { current ->
- current.copy(
- translationX = { childViews[burnInLayerId]?.translationX },
- translationY = { childViews[burnInLayerId]?.translationY },
- )
- }
+ burnInParams.update { current ->
+ current.copy(
+ translationX = { childViews[burnInLayerId]?.translationX },
+ translationY = { childViews[burnInLayerId]?.translationY },
+ )
}
disposables +=
@@ -515,20 +498,16 @@ object KeyguardRootViewBinder {
burnInParams.update { current ->
current.copy(
minViewY =
- if (MigrateClocksToBlueprint.isEnabled) {
- // To ensure burn-in doesn't enroach the top inset, get the min top Y
- childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
- min(
- currentMin,
- if (!isUserVisible(view)) {
- Int.MAX_VALUE
- } else {
- view.getTop()
- },
- )
- }
- } else {
- childViews[statusViewId]?.top ?: 0
+ // To ensure burn-in doesn't enroach the top inset, get the min top Y
+ childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
+ min(
+ currentMin,
+ if (!isUserVisible(view)) {
+ Int.MAX_VALUE
+ } else {
+ view.getTop()
+ },
+ )
}
)
}
@@ -542,28 +521,6 @@ object KeyguardRootViewBinder {
}
}
- suspend fun bindAodNotifIconVisibility(
- view: View,
- isVisible: Flow<AnimatedValue<Boolean>>,
- configuration: ConfigurationState,
- screenOffAnimationController: ScreenOffAnimationController,
- ) {
- if (MigrateClocksToBlueprint.isEnabled) {
- throw IllegalStateException("should only be called in legacy code paths")
- }
- coroutineScope {
- val iconAppearTranslationPx =
- configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
- isVisible.collect { isVisible ->
- view.setAodNotifIconContainerIsVisible(
- isVisible = isVisible,
- iconsAppearTranslationPx = iconAppearTranslationPx.value,
- screenOffAnimationController = screenOffAnimationController,
- )
- }
- }
- }
-
private fun View.setAodNotifIconContainerIsVisible(
isVisible: AnimatedValue<Boolean>,
iconsAppearTranslationPx: Int,
@@ -578,9 +535,6 @@ object KeyguardRootViewBinder {
}
when {
!isVisible.isAnimating -> {
- if (!MigrateClocksToBlueprint.isEnabled) {
- translationY = 0f
- }
visibility =
if (isVisible.value) {
alpha = 1f
@@ -591,7 +545,6 @@ object KeyguardRootViewBinder {
}
}
else -> {
- animateInIconTranslation()
if (isVisible.value) {
CrossFadeHelper.fadeIn(this, animatorListener)
} else {
@@ -601,19 +554,10 @@ object KeyguardRootViewBinder {
}
}
- private fun View.animateInIconTranslation() {
- if (!MigrateClocksToBlueprint.isEnabled) {
- animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
- }
- }
-
private fun MotionEvent.isTouchscreenSource(): Boolean {
return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true
}
- private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
- setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
-
private val statusViewId = R.id.keyguard_status_view
private val burnInLayerId = R.id.burn_in_layer
private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index de4a1b03203c..213083db71c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -22,7 +22,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
@@ -44,13 +43,12 @@ object KeyguardSmartspaceViewBinder {
return keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
->
updateDateWeatherToBurnInLayer(
keyguardRootView,
clockViewModel,
- smartspaceViewModel
+ smartspaceViewModel,
)
blueprintInteractor.refreshBlueprint(
Config(
@@ -63,7 +61,6 @@ object KeyguardSmartspaceViewBinder {
}
launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
blueprintInteractor.refreshBlueprint(
@@ -100,7 +97,7 @@ object KeyguardSmartspaceViewBinder {
private fun updateDateWeatherToBurnInLayer(
keyguardRootView: ConstraintLayout,
clockViewModel: KeyguardClockViewModel,
- smartspaceViewModel: KeyguardSmartspaceViewModel
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
if (clockViewModel.hasCustomWeatherDataDisplay.value) {
removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
@@ -112,7 +109,7 @@ object KeyguardSmartspaceViewBinder {
private fun addDateWeatherToBurnInLayer(
constraintLayout: ConstraintLayout,
- smartspaceViewModel: KeyguardSmartspaceViewModel
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
burnInLayer.apply {
@@ -129,7 +126,7 @@ object KeyguardSmartspaceViewBinder {
private fun removeDateWeatherFromBurnInLayer(
constraintLayout: ConstraintLayout,
- smartspaceViewModel: KeyguardSmartspaceViewModel
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
burnInLayer.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 4c23adfe92e8..6f7872c9cb96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,7 +30,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.customization.R as customR
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.res.R
@@ -62,9 +61,6 @@ constructor(
private lateinit var nic: NotificationIconContainer
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
nic =
NotificationIconContainer(context, null).apply {
id = nicId
@@ -81,10 +77,6 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
nicBindingDisposable?.dispose()
nicBindingDisposable =
NotificationIconContainerViewBinder.bindWhileAttached(
@@ -98,10 +90,6 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
val bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
val isVisible = rootViewModel.isNotifIconContainerVisible.value
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 70ca82492775..dccf61d4e6c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -1369,8 +1369,9 @@ public class MediaControlPanel {
boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing;
int notVisibleValue;
- if ((buttonId == R.id.actionPrev && semanticActions.getReservePrev())
- || (buttonId == R.id.actionNext && semanticActions.getReserveNext())) {
+ if (!shouldBeHiddenDueToScrubbing
+ && ((buttonId == R.id.actionPrev && semanticActions.getReservePrev())
+ || (buttonId == R.id.actionNext && semanticActions.getReserveNext()))) {
notVisibleValue = ConstraintSet.INVISIBLE;
mMediaViewHolder.getAction(buttonId).setFocusable(visible);
mMediaViewHolder.getAction(buttonId).setClickable(visible);
@@ -1408,7 +1409,9 @@ public class MediaControlPanel {
// The scrubbing time views replace the SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING action views,
// so we should only allow scrubbing times to be shown if those action views are present.
return semanticActions != null && SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.stream().allMatch(
- id -> semanticActions.getActionById(id) != null
+ id -> (semanticActions.getActionById(id) != null
+ || ((id == R.id.actionPrev && semanticActions.getReservePrev())
+ || (id == R.id.actionNext && semanticActions.getReserveNext())))
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 4e97f2015c12..61e4d95a88e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -316,8 +316,11 @@ class MediaControlViewModel(
isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
notVisibleValue =
if (
- (buttonId == R.id.actionPrev && model.semanticActionButtons!!.reservePrev) ||
- (buttonId == R.id.actionNext && model.semanticActionButtons!!.reserveNext)
+ !shouldHideWhenScrubbing &&
+ ((buttonId == R.id.actionPrev &&
+ model.semanticActionButtons!!.reservePrev) ||
+ (buttonId == R.id.actionNext &&
+ model.semanticActionButtons!!.reserveNext))
) {
ConstraintSet.INVISIBLE
} else {
@@ -382,7 +385,9 @@ class MediaControlViewModel(
// so we should only allow scrubbing times to be shown if those action views are present.
return semanticActions?.let {
SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.stream().allMatch { id: Int ->
- semanticActions.getActionById(id) != null
+ semanticActions.getActionById(id) != null ||
+ (id == R.id.actionPrev && semanticActions.reservePrev ||
+ id == R.id.actionNext && semanticActions.reserveNext)
}
} ?: false
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 1204cde19c76..2a23620839e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -74,7 +74,7 @@ constructor(
context: Context,
logger: MediaTttReceiverLogger,
viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
- @Main mainExecutor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
dumpManager: DumpManager,
@@ -285,6 +285,14 @@ constructor(
} else {
rippleController.collapseRipple(rippleView, onAnimationEnd)
animateViewTranslationAndFade(iconContainerView, translationYBy, 0f)
+ mainExecutor.executeDelayed(
+ {
+ if (view.isAttachedToWindow) {
+ onAnimationEnd.run()
+ }
+ },
+ ICON_TRANSLATION_ANIM_DURATION,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b1719107fae1..037a1b2a97f1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -26,6 +26,7 @@ import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted;
import static java.util.stream.Collectors.joining;
@@ -965,11 +966,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
return mDesktopModeExcludeRegion.contains(x, y);
}
- private boolean isWithinTouchRegion(int x, int y) {
+ private boolean isWithinTouchRegion(MotionEvent ev) {
// If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
// gesture
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y);
- final boolean isInDesktopExcludeRegion = desktopExcludeRegionContains(x, y);
+ final boolean isInDesktopExcludeRegion = desktopExcludeRegionContains(x, y)
+ && isEdgeResizePermitted(ev);
if (isInsidePip || isInDesktopExcludeRegion
|| mNavBarOverlayExcludedBounds.contains(x, y)) {
return false;
@@ -1098,8 +1102,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
&& isValidTrackpadBackGesture(true /* isTrackpadEvent */);
} else {
mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
- && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
- && !isButtonPressFromTrackpad(ev);
+ && isWithinTouchRegion(ev) && !isButtonPressFromTrackpad(ev);
}
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index dc188c24e02b..e8ee4dd8ebce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -26,6 +26,7 @@ import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.systemui.FontStyles;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
@@ -68,7 +69,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
mBuildText = mView.findViewById(R.id.build);
if (gsfQuickSettings()) {
- mBuildText.setTypeface(Typeface.create("gsf-body-medium", Typeface.NORMAL));
+ mBuildText.setTypeface(Typeface.create(FontStyles.GSF_BODY_MEDIUM, Typeface.NORMAL));
}
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
mEditButton = mView.findViewById(android.R.id.edit);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index db778a208b1e..873059ee08db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -47,6 +47,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.FontSizeUtils;
+import com.android.systemui.FontStyles;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.qs.QSEditEvent;
@@ -314,7 +315,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
v.setMinimumHeight(calculateHeaderMinHeight(context));
if (gsfQuickSettings()) {
((TextView) v.findViewById(android.R.id.title)).setTypeface(
- Typeface.create("gsf-label-large", Typeface.NORMAL));
+ Typeface.create(FontStyles.GSF_LABEL_LARGE, Typeface.NORMAL));
}
return new Holder(v);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index b7ebce247ec9..d401b6ecbfd8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -60,6 +60,7 @@ import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
import com.android.systemui.Flags
import com.android.systemui.FontSizeUtils
+import com.android.systemui.FontStyles
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
@@ -312,9 +313,11 @@ constructor(
if (Flags.gsfQuickSettings()) {
label.apply {
- typeface = Typeface.create("gsf-title-small-emphasized", Typeface.NORMAL)
+ typeface = Typeface.create(FontStyles.GSF_TITLE_SMALL_EMPHASIZED, Typeface.NORMAL)
+ }
+ secondaryLabel.apply {
+ typeface = Typeface.create(FontStyles.GSF_LABEL_MEDIUM, Typeface.NORMAL)
}
- secondaryLabel.apply { typeface = Typeface.create("gsf-label-medium", Typeface.NORMAL) }
}
addView(labelContainer)
@@ -776,11 +779,15 @@ constructor(
lastIconTint = icon.getColor(state)
// Long-press effects
- longPressEffect?.qsTile?.state?.handlesLongClick = state.handlesLongClick
- if (
- state.handlesLongClick &&
- longPressEffect?.initializeEffect(longPressEffectDuration) == true
- ) {
+ updateLongPressEffect(state.handlesLongClick)
+ }
+
+ private fun updateLongPressEffect(handlesLongClick: Boolean) {
+ // The long press effect in the tile can't be updated if it is still running
+ if (longPressEffect?.state != QSLongPressEffect.State.IDLE) return
+
+ longPressEffect.qsTile?.state?.handlesLongClick = handlesLongClick
+ if (handlesLongClick && longPressEffect.initializeEffect(longPressEffectDuration)) {
showRippleEffect = false
longPressEffect.qsTile?.state?.state = lastState // Store the tile's state
longPressEffect.resetState()
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
index 2d6181aa04af..1355ba8bdfd4 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
@@ -47,7 +47,11 @@ internal constructor(
}
override fun createDialog(): SystemUIDialog {
- return systemUIDialogFactory.create(this, rearDisplayContext)
+ return systemUIDialogFactory.create(
+ this,
+ rearDisplayContext,
+ false, /* shouldAcsdDismissDialog */
+ )
}
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index d83d74e4e538..0e6fc36fb96a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -230,13 +230,7 @@ constructor(
) {
val currentSceneKey = currentScene.value
val resolvedScene = sceneFamilyResolvers.get()[toScene]?.resolvedScene?.value ?: toScene
- if (
- !validateSceneChange(
- from = currentSceneKey,
- to = resolvedScene,
- loggingReason = loggingReason,
- )
- ) {
+ if (!validateSceneChange(to = resolvedScene, loggingReason = loggingReason)) {
return
}
@@ -268,13 +262,7 @@ constructor(
familyResolver.resolvedScene.value
}
} ?: toScene
- if (
- !validateSceneChange(
- from = currentSceneKey,
- to = resolvedScene,
- loggingReason = loggingReason,
- )
- ) {
+ if (!validateSceneChange(to = resolvedScene, loggingReason = loggingReason)) {
return
}
@@ -458,12 +446,11 @@ constructor(
* Will throw a runtime exception for illegal states (for example, attempting to change to a
* scene that's not part of the current scene framework configuration).
*
- * @param from The current scene being transitioned away from
* @param to The desired destination scene to transition to
* @param loggingReason The reason why the transition is requested, for logging purposes
* @return `true` if the scene change is valid; `false` if it shouldn't happen
*/
- private fun validateSceneChange(from: SceneKey, to: SceneKey, loggingReason: String): Boolean {
+ private fun validateSceneChange(to: SceneKey, loggingReason: String): Boolean {
if (to !in repository.allContentKeys) {
return false
}
@@ -486,7 +473,7 @@ constructor(
" Logging reason for scene change was: $loggingReason"
}
- return from != to
+ return true
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index b89eb5c762e0..2a0a22f32601 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.combine
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -34,7 +35,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/**
@@ -59,6 +59,7 @@ constructor(
deviceEntryInteractor.isDeviceEntered,
deviceEntryInteractor.isUnlocked,
keyguardInteractor.isDreamingWithOverlay,
+ keyguardInteractor.isAbleToDream,
transform = ::homeScene,
)
.stateIn(
@@ -71,6 +72,7 @@ constructor(
isDeviceEntered = deviceEntryInteractor.isDeviceEntered.value,
isUnlocked = deviceEntryInteractor.isUnlocked.value,
isDreamingWithOverlay = false,
+ isAbleToDream = false,
),
)
@@ -82,10 +84,11 @@ constructor(
isDeviceEntered: Boolean,
isUnlocked: Boolean,
isDreamingWithOverlay: Boolean,
+ isAbleToDream: Boolean,
): SceneKey =
when {
// Dream can run even if Keyguard is disabled, thus it has the highest priority here.
- isDreamingWithOverlay -> Scenes.Dream
+ isDreamingWithOverlay && isAbleToDream -> Scenes.Dream
!isKeyguardEnabled -> Scenes.Gone
canSwipeToEnter == true -> Scenes.Lockscreen
!isDeviceEntered -> Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 90d27f4b33e9..c65c3b854b82 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,7 +21,6 @@ import static com.android.settingslib.display.BrightnessUtils.convertGammaToLine
import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
import android.animation.ValueAnimator;
-import android.annotation.NonNull;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
@@ -42,6 +41,7 @@ import android.service.vr.IVrStateCallbacks;
import android.util.Log;
import android.util.MathUtils;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -72,19 +72,19 @@ import java.util.concurrent.Executor;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
- private static final int SLIDER_ANIMATION_DURATION = 3000;
+ protected static final int SLIDER_ANIMATION_DURATION = 3000;
- private static final int MSG_UPDATE_SLIDER = 1;
- private static final int MSG_ATTACH_LISTENER = 2;
- private static final int MSG_DETACH_LISTENER = 3;
- private static final int MSG_VR_MODE_CHANGED = 4;
+ protected static final int MSG_UPDATE_SLIDER = 1;
+ protected static final int MSG_ATTACH_LISTENER = 2;
+ protected static final int MSG_DETACH_LISTENER = 3;
+ protected static final int MSG_VR_MODE_CHANGED = 4;
- private static final Uri BRIGHTNESS_MODE_URI =
+ protected static final Uri BRIGHTNESS_MODE_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
private final int mDisplayId;
private final Context mContext;
- private final ToggleSlider mControl;
+ protected final ToggleSlider mControl;
private final DisplayManager mDisplayManager;
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
@@ -109,10 +109,10 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
private boolean mTrackingTouch = false; // Brightness adjusted via touch events.
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
- private boolean mExternalChange;
+ protected boolean mExternalChange;
private boolean mControlValueInitialized;
- private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
- private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
+ protected float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
+ protected float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
private boolean mIsBrightnessOverriddenByWindow = false;
private ValueAnimator mSliderAnimator;
@@ -253,10 +253,8 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
if (info == null) {
return;
}
- mBrightnessMax = info.brightnessMaximum;
- mBrightnessMin = info.brightnessMinimum;
- mIsBrightnessOverriddenByWindow = info.isBrightnessOverrideByWindow;
+ updateBrightnessInfo(info);
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(info.brightness);
mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
@@ -264,6 +262,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
};
+ protected void updateBrightnessInfo(BrightnessInfo info) {
+ mBrightnessMax = info.brightnessMaximum;
+ mBrightnessMin = info.brightnessMinimum;
+ mIsBrightnessOverriddenByWindow = info.isBrightnessOverrideByWindow;
+ }
+
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -301,7 +305,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
};
- private final Handler mMainHandler;
+ protected final Handler mMainHandler;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -459,7 +463,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
return !mAutomatic && !mTrackingTouch;
}
- private void updateSlider(float brightnessValue, boolean inVrMode) {
+ protected void updateSlider(float brightnessValue, boolean inVrMode) {
final float min = mBrightnessMin;
final float max = mBrightnessMax;
@@ -502,12 +506,17 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
mSliderAnimator.start();
}
-
+ /** Factory interface for creating a {@link BrightnessController}. */
+ public interface Factory {
+ @NonNull
+ BrightnessController create(ToggleSlider toggleSlider);
+ }
/** Factory for creating a {@link BrightnessController}. */
@AssistedFactory
- public interface Factory {
+ public interface BrightnessControllerFactory extends Factory {
/** Create a {@link BrightnessController} */
+ @NonNull
BrightnessController create(ToggleSlider toggleSlider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 503d0bfbc301..02eca74fd751 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -25,6 +25,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
@@ -42,6 +43,7 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.SystemClock;
+
import com.google.android.msdl.domain.MSDLPlayer;
import javax.inject.Inject;
@@ -89,7 +91,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
}
};
- BrightnessSliderController(
+ protected BrightnessSliderController(
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
@@ -247,16 +249,20 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
return mView.isVisibleToUser();
}
+ protected void handleSliderProgressChange(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mListener != null) {
+ mListener.onChanged(mTracking, progress, false);
+ if (fromUser) {
+ mBrightnessSliderHapticPlugin.onProgressChanged(progress, true);
+ }
+ }
+ }
+
private final SeekBar.OnSeekBarChangeListener mSeekListener =
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (mListener != null) {
- mListener.onChanged(mTracking, progress, false);
- if (fromUser) {
- mBrightnessSliderHapticPlugin.onProgressChanged(progress, true);
- }
- }
+ handleSliderProgressChange(seekBar, progress, fromUser);
}
@Override
@@ -289,11 +295,21 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
}
};
+ /** Factory interface for creating a {@link BrightnessSliderController}. */
+ public interface Factory {
+ @NonNull
+ BrightnessSliderController create(
+ Context context,
+ @Nullable ViewGroup viewRoot);
+
+ int getLayout();
+ }
+
/**
* Creates a {@link BrightnessSliderController} with its associated view.
*/
- public static class Factory {
+ public static class BrightnessSliderControllerFactory implements Factory {
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
@@ -303,7 +319,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
private final BrightnessWarningToast mBrightnessWarningToast;
@Inject
- public Factory(
+ public BrightnessSliderControllerFactory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
@@ -328,6 +344,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
* @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
* hierarchy will not be attached
*/
+ @Override
+ @NonNull
public BrightnessSliderController create(
Context context,
@Nullable ViewGroup viewRoot) {
@@ -345,7 +363,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
}
/** Get the layout to inflate based on what slider to use */
- private int getLayout() {
+ public int getLayout() {
return R.layout.quick_settings_brightness_dialog;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index a39d25a5ef30..550ac62c0bde 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -43,12 +43,12 @@ import java.util.Collections;
public class BrightnessSliderView extends FrameLayout {
@NonNull
- private ToggleSeekBar mSlider;
+ protected ToggleSeekBar mSlider;
private DispatchTouchEventListener mListener;
private Gefingerpoken mOnInterceptListener;
@Nullable
- private Drawable mProgressDrawable;
- private float mScale = 1f;
+ protected Drawable mProgressDrawable;
+ protected float mScale = 1f;
private final Rect mSystemGestureExclusionRect = new Rect();
public BrightnessSliderView(Context context) {
@@ -65,6 +65,10 @@ public class BrightnessSliderView extends FrameLayout {
super.onFinishInflate();
setLayerType(LAYER_TYPE_HARDWARE, null);
+ initBrightnessViewComponents();
+ }
+
+ protected void initBrightnessViewComponents() {
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
setBoundaryOffset();
@@ -81,7 +85,7 @@ public class BrightnessSliderView extends FrameLayout {
}
}
- private void setBoundaryOffset() {
+ protected void setBoundaryOffset() {
// BrightnessSliderView uses hardware layer; if the background of its children exceed its
// boundary, it'll be cropped. We need to expand its boundary so that the background of
// ToggleSeekBar (i.e. the focus state) can be correctly rendered.
@@ -131,7 +135,7 @@ public class BrightnessSliderView extends FrameLayout {
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
- void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+ protected void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
mSlider.setAdminBlocker(blocker);
}
@@ -211,7 +215,7 @@ public class BrightnessSliderView extends FrameLayout {
}
}
- private void applySliderScale() {
+ protected void applySliderScale() {
if (mProgressDrawable != null) {
final Rect r = mProgressDrawable.getBounds();
int height = (int) (mProgressDrawable.getIntrinsicHeight() * mScale);
@@ -229,7 +233,7 @@ public class BrightnessSliderView extends FrameLayout {
* Interface to attach a listener for {@link View#dispatchTouchEvent}.
*/
@FunctionalInterface
- interface DispatchTouchEventListener {
+ public interface DispatchTouchEventListener {
boolean onDispatchTouchEvent(MotionEvent ev);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index c241f2165c8f..a0985fc61b07 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -85,12 +85,12 @@ public class ToggleSeekBar extends SeekBar {
}
}
- void setAdminBlocker(AdminBlocker blocker) {
+ public void setAdminBlocker(AdminBlocker blocker) {
mAdminBlocker = blocker;
setEnabled(blocker == null);
}
- interface AdminBlocker {
+ public interface AdminBlocker {
boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/dagger/BrightnessSliderModule.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/dagger/BrightnessSliderModule.kt
new file mode 100644
index 000000000000..fbe442eb4b5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/dagger/BrightnessSliderModule.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.settings.brightness.dagger
+
+import com.android.systemui.settings.brightness.BrightnessController
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class BrightnessSliderModule {
+
+ @Binds
+ abstract fun bindBrightnessSliderControllerFactory(
+ factory: BrightnessSliderController.BrightnessSliderControllerFactory
+ ): BrightnessSliderController.Factory
+
+ @Binds
+ abstract fun bindBrightnessControllerFactory(
+ factory: BrightnessController.BrightnessControllerFactory
+ ): BrightnessController.Factory
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 71efbab0f738..3180a06ae787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -14,19 +14,49 @@
package com.android.systemui.statusbar;
+import android.annotation.IntDef;
import android.content.pm.UserInfo;
import android.util.SparseArray;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
public interface NotificationLockscreenUserManager {
String PERMISSION_SELF = "com.android.systemui.permission.SELF";
String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
= "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ prefix = {"REDACTION_TYPE_"},
+ value = {
+ REDACTION_TYPE_NONE,
+ REDACTION_TYPE_PUBLIC,
+ REDACTION_TYPE_SENSITIVE_CONTENT})
+ @interface RedactionType {}
+
+ /**
+ * Indicates that a notification requires no redaction
+ */
+ int REDACTION_TYPE_NONE = 0;
+
+ /**
+ * Indicates that a notification should have all content redacted, showing the public view.
+ * Overrides all other redaction types.
+ */
+ int REDACTION_TYPE_PUBLIC = 1;
+
+ /**
+ * Indicates that a notification should have its main content redacted, due to detected
+ * sensitive content, such as a One-Time Password
+ */
+ int REDACTION_TYPE_SENSITIVE_CONTENT = 1 << 1;
+
/**
* @param userId user Id
- * @return true if we re on a secure lock screen
+ * @return true if we're on a secure lock screen
*/
boolean isLockscreenPublicMode(int userId);
@@ -68,7 +98,13 @@ public interface NotificationLockscreenUserManager {
void updatePublicMode();
- boolean needsRedaction(NotificationEntry entry);
+ /**
+ * Determine what type of redaction is needed, if any. Returns REDACTION_TYPE_NONE if no
+ * redaction type is needed, REDACTION_TYPE_PUBLIC if private notifications are blocked, and
+ * REDACTION_TYPE_SENSITIVE_CONTENT if sensitive content is detected, and REDACTION_TYPE_PUBLIC
+ * doesn't apply.
+ */
+ @RedactionType int getRedactionType(NotificationEntry entry);
/**
* Has the given user chosen to allow their private (full) notifications to be shown even
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index a79b78f8d5f3..239257d357f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -654,8 +654,13 @@ public class NotificationLockscreenUserManagerImpl implements
}
}
- /** @return true if the entry needs redaction when on the lockscreen. */
- public boolean needsRedaction(NotificationEntry ent) {
+ /**
+ * Determine what type of redaction is needed, if any. Returns REDACTION_TYPE_NONE if no
+ * redaction type is needed, REDACTION_TYPE_PUBLIC if private notifications are blocked, and
+ * REDACTION_TYPE_SENSITIVE_CONTENT if sensitive content is detected, and REDACTION_TYPE_PUBLIC
+ * doesn't apply.
+ */
+ public @RedactionType int getRedactionType(NotificationEntry ent) {
int userId = ent.getSbn().getUserId();
boolean isCurrentUserRedactingNotifs =
@@ -675,13 +680,19 @@ public class NotificationLockscreenUserManagerImpl implements
ent.isNotificationVisibilityPrivate();
boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
- if (keyguardPrivateNotifications()) {
- return !mKeyguardAllowingNotifications || isNotifSensitive
- || userForcesRedaction || (notificationRequestsRedaction && isNotifRedacted);
- } else {
- return userForcesRedaction || isNotifSensitive
- || (notificationRequestsRedaction && isNotifRedacted);
+ if (userForcesRedaction) {
+ return REDACTION_TYPE_PUBLIC;
+ }
+ if (notificationRequestsRedaction && isNotifRedacted) {
+ return REDACTION_TYPE_PUBLIC;
+ }
+ if (keyguardPrivateNotifications() && !mKeyguardAllowingNotifications) {
+ return REDACTION_TYPE_PUBLIC;
+ }
+ if (isNotifSensitive) {
+ return REDACTION_TYPE_SENSITIVE_CONTENT;
}
+ return REDACTION_TYPE_NONE;
}
private boolean packageHasVisibilityOverride(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 2588c7ae2363..46c84fbc19aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
@@ -101,6 +102,19 @@ interface StatusBarModule {
@Provides
@SysUISingleton
+ @IntoMap
+ @ClassKey(OngoingCallInteractor::class)
+ fun ongoingCallInteractor(
+ interactor: OngoingCallInteractor
+ ): CoreStartable =
+ if (StatusBarChipsModernization.isEnabled) {
+ interactor
+ } else {
+ CoreStartable.NOP
+ }
+
+ @Provides
+ @SysUISingleton
fun lightBarController(store: LightBarControllerStore): LightBarController {
return store.defaultDisplay
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index f75163d2662b..2ecce1f02ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -416,7 +416,7 @@ public class PreparationCoordinator implements Coordinator {
/* showSnooze = */ adjustment.isSnoozeEnabled(),
/* isChildInGroup = */ adjustment.isChildInGroup(),
/* isGroupSummary = */ adjustment.isGroupSummary(),
- /* needsRedaction = */ adjustment.getNeedsRedaction()
+ /* needsRedaction = */ adjustment.getRedactionType()
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 04458f3a7168..1875e7e46693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -30,6 +30,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -211,7 +212,8 @@ constructor(
screenshareNotificationHiding() &&
sensitiveNotificationProtectionController.shouldProtectNotification(entry)
- val needsRedaction = lockscreenUserManager.needsRedaction(entry)
+ val needsRedaction =
+ lockscreenUserManager.getRedactionType(entry) != REDACTION_TYPE_NONE
val isSensitive = userPublic && needsRedaction
entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
if (screenshareNotificationHiding()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index ff72888a5c26..ff9d533a09d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.render.NotifViewController
@@ -61,6 +62,6 @@ interface NotifInflater {
val showSnooze: Boolean,
val isChildInGroup: Boolean = false,
val isGroupSummary: Boolean = false,
- val needsRedaction: Boolean,
+ @RedactionType val redactionType: Int,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index e70fb6b0fdf3..331ef1c01596 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,22 +20,24 @@ import android.app.Notification
import android.app.RemoteInput
import android.graphics.drawable.Icon
import android.text.TextUtils
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
/**
* An immutable object which contains minimal state extracted from an entry that represents state
- * which can change without a direct app update (e.g. with a ranking update).
- * Diffing two entries determines if view re-inflation is needed.
+ * which can change without a direct app update (e.g. with a ranking update). Diffing two entries
+ * determines if view re-inflation is needed.
*/
-class NotifUiAdjustment internal constructor(
+class NotifUiAdjustment
+internal constructor(
val key: String,
val smartActions: List<Notification.Action>,
val smartReplies: List<CharSequence>,
val isConversation: Boolean,
val isSnoozeEnabled: Boolean,
val isMinimized: Boolean,
- val needsRedaction: Boolean,
+ @RedactionType val redactionType: Int,
val isChildInGroup: Boolean,
val isGroupSummary: Boolean,
) {
@@ -43,65 +45,72 @@ class NotifUiAdjustment internal constructor(
@JvmStatic
fun needReinflate(
oldAdjustment: NotifUiAdjustment,
- newAdjustment: NotifUiAdjustment
- ): Boolean = when {
- oldAdjustment === newAdjustment -> false
- oldAdjustment.isConversation != newAdjustment.isConversation -> true
- oldAdjustment.isSnoozeEnabled != newAdjustment.isSnoozeEnabled -> true
- oldAdjustment.isMinimized != newAdjustment.isMinimized -> true
- oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
- areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
- newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
- AsyncHybridViewInflation.isEnabled &&
- !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
- AsyncGroupHeaderViewInflation.isEnabled &&
- !oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true
- else -> false
- }
+ newAdjustment: NotifUiAdjustment,
+ ): Boolean =
+ when {
+ oldAdjustment === newAdjustment -> false
+ oldAdjustment.isConversation != newAdjustment.isConversation -> true
+ oldAdjustment.isSnoozeEnabled != newAdjustment.isSnoozeEnabled -> true
+ oldAdjustment.isMinimized != newAdjustment.isMinimized -> true
+ oldAdjustment.redactionType != newAdjustment.redactionType -> true
+ areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
+ newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
+ AsyncHybridViewInflation.isEnabled &&
+ !oldAdjustment.isChildInGroup &&
+ newAdjustment.isChildInGroup -> true
+ AsyncGroupHeaderViewInflation.isEnabled &&
+ !oldAdjustment.isGroupSummary &&
+ newAdjustment.isGroupSummary -> true
+ else -> false
+ }
private fun areDifferent(
first: List<Notification.Action>,
- second: List<Notification.Action>
- ): Boolean = when {
- first === second -> false
- first.size != second.size -> true
- else -> first.asSequence().zip(second.asSequence()).any {
- (!TextUtils.equals(it.first.title, it.second.title)) ||
- (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
- (it.first.actionIntent != it.second.actionIntent) ||
- (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
+ second: List<Notification.Action>,
+ ): Boolean =
+ when {
+ first === second -> false
+ first.size != second.size -> true
+ else ->
+ first.asSequence().zip(second.asSequence()).any {
+ (!TextUtils.equals(it.first.title, it.second.title)) ||
+ (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
+ (it.first.actionIntent != it.second.actionIntent) ||
+ (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
+ }
}
- }
- private fun areDifferent(first: Icon?, second: Icon?): Boolean = when {
- first === second -> false
- first == null || second == null -> true
- else -> !first.sameAs(second)
- }
+ private fun areDifferent(first: Icon?, second: Icon?): Boolean =
+ when {
+ first === second -> false
+ first == null || second == null -> true
+ else -> !first.sameAs(second)
+ }
- private fun areDifferent(
- first: Array<RemoteInput>?,
- second: Array<RemoteInput>?
- ): Boolean = when {
- first === second -> false
- first == null || second == null -> true
- first.size != second.size -> true
- else -> first.asSequence().zip(second.asSequence()).any {
- (!TextUtils.equals(it.first.label, it.second.label)) ||
- (areDifferent(it.first.choices, it.second.choices))
+ private fun areDifferent(first: Array<RemoteInput>?, second: Array<RemoteInput>?): Boolean =
+ when {
+ first === second -> false
+ first == null || second == null -> true
+ first.size != second.size -> true
+ else ->
+ first.asSequence().zip(second.asSequence()).any {
+ (!TextUtils.equals(it.first.label, it.second.label)) ||
+ (areDifferent(it.first.choices, it.second.choices))
+ }
}
- }
private fun areDifferent(
first: Array<CharSequence>?,
- second: Array<CharSequence>?
- ): Boolean = when {
- first === second -> false
- first == null || second == null -> true
- first.size != second.size -> true
- else -> first.asSequence().zip(second.asSequence()).any {
- !TextUtils.equals(it.first, it.second)
+ second: Array<CharSequence>?,
+ ): Boolean =
+ when {
+ first === second -> false
+ first == null || second == null -> true
+ first.size != second.size -> true
+ else ->
+ first.asSequence().zip(second.asSequence()).any {
+ !TextUtils.equals(it.first, it.second)
+ }
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 4c82bc193c77..97e55c19d2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
@@ -79,7 +80,7 @@ constructor(
secureSettings.registerContentObserverForUserSync(
SHOW_NOTIFICATION_SNOOZE,
settingsObserver,
- UserHandle.USER_ALL
+ UserHandle.USER_ALL,
)
}
dirtyListeners.addIfAbsent(listener)
@@ -140,10 +141,15 @@ constructor(
isConversation = entry.ranking.isConversation,
isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
isMinimized = isEntryMinimized(entry),
- needsRedaction =
- lockscreenUserManager.needsRedaction(entry) ||
- (screenshareNotificationHiding() &&
- sensitiveNotifProtectionController.shouldProtectNotification(entry)),
+ redactionType =
+ if (
+ screenshareNotificationHiding() &&
+ sensitiveNotifProtectionController.shouldProtectNotification(entry)
+ ) {
+ REDACTION_TYPE_PUBLIC
+ } else {
+ lockscreenUserManager.getRedactionType(entry)
+ },
isChildInGroup = entry.hasEverBeenGroupChild(),
isGroupSummary = entry.hasEverBeenGroupSummary(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index e6d22b07f3ab..80e8f55b897a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -256,9 +256,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseMinimized(isMinimized);
- boolean needsRedaction = screenshareNotificationHiding()
- ? inflaterParams.getNeedsRedaction()
- : mNotificationLockscreenUserManager.needsRedaction(entry);
+ // TODO b/358403414: use the different types of redaction
+ boolean needsRedaction = inflaterParams.getRedactionType() != REDACTION_TYPE_NONE;
if (needsRedaction) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 6aa8d0ab3d1c..6756077e5444 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -47,6 +47,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
@@ -65,6 +66,11 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
@@ -78,11 +84,6 @@ import java.util.stream.Stream;
import javax.inject.Inject;
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
@@ -93,15 +94,15 @@ public class HeadsUpManagerImpl
private static final String TAG = "BaseHeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
private static final String REASON_REORDER_ALLOWED = "mOnReorderingAllowedListener";
- protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
+ private final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
- protected final Context mContext;
+ private final Context mContext;
- protected int mTouchAcceptanceDelay;
- protected int mSnoozeLengthMs;
- protected boolean mHasPinnedNotification;
+ private final int mTouchAcceptanceDelay;
+ private int mSnoozeLengthMs;
+ private boolean mHasPinnedNotification;
private PinnedStatus mPinnedNotificationStatus = PinnedStatus.NotPinned;
- protected int mUser;
+ private int mUser;
private final ArrayMap<String, Long> mSnoozedPackages;
private final AccessibilityManagerWrapper mAccessibilityMgr;
@@ -113,13 +114,14 @@ public class HeadsUpManagerImpl
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
private final VisualStabilityProvider mVisualStabilityProvider;
- protected final SystemClock mSystemClock;
- protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
- protected final HeadsUpManagerLogger mLogger;
- protected int mMinimumDisplayTime;
- protected int mStickyForSomeTimeAutoDismissTime;
- protected int mAutoDismissTime;
- protected DelayableExecutor mExecutor;
+ private final SystemClock mSystemClock;
+ @VisibleForTesting
+ final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+ private final HeadsUpManagerLogger mLogger;
+ private final int mMinimumDisplayTime;
+ private final int mStickyForSomeTimeAutoDismissTime;
+ private final int mAutoDismissTime;
+ private final DelayableExecutor mExecutor;
private final int mExtensionTime;
@@ -133,7 +135,7 @@ public class HeadsUpManagerImpl
private final HashSet<String> mSwipedOutKeys = new HashSet<>();
private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
@VisibleForTesting
- public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+ final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
private boolean mReleaseOnExpandFinish;
@@ -383,9 +385,7 @@ public class HeadsUpManagerImpl
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotificationRequest(key, requestedPinnedStatus, headsUpEntry != null);
- Runnable runnable = () -> {
- updateNotificationInternal(key, requestedPinnedStatus);
- };
+ Runnable runnable = () -> updateNotificationInternal(key, requestedPinnedStatus);
mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
}
@@ -407,9 +407,7 @@ public class HeadsUpManagerImpl
headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
PinnedStatus pinnedStatus =
getNewPinnedStatusForEntry(headsUpEntry, requestedPinnedStatus);
- if (headsUpEntry != null) {
- setEntryPinned(headsUpEntry, pinnedStatus, "updateNotificationInternal");
- }
+ setEntryPinned(headsUpEntry, pinnedStatus, "updateNotificationInternal");
}
}
@@ -515,8 +513,7 @@ public class HeadsUpManagerImpl
}
/**
- * @param key
- * @return When a HUN entry should be removed in milliseconds from now
+ * @return When a HUN entry with the given key should be removed in milliseconds from now
*/
@Override
public long getEarliestRemovalTime(String key) {
@@ -528,7 +525,10 @@ public class HeadsUpManagerImpl
}
@VisibleForTesting
- protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
+ boolean shouldHeadsUpBecomePinned(@Nullable NotificationEntry entry) {
+ if (entry == null) {
+ return false;
+ }
boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
if (SceneContainerFlag.isEnabled()) {
pin |= mIsQsExpanded;
@@ -549,24 +549,18 @@ public class HeadsUpManagerImpl
return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
}
- protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
- if (entry == null) {
- return false;
- }
- if (entry.getSbn() == null) {
- return false;
- }
+ private boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
if (entry.getSbn().getNotification() == null) {
return false;
}
return entry.getSbn().getNotification().fullScreenIntent != null;
}
- protected void setEntryPinned(
+ private void setEntryPinned(
@NonNull HeadsUpManagerImpl.HeadsUpEntry headsUpEntry, PinnedStatus pinnedStatus,
String reason) {
- mLogger.logSetEntryPinned(headsUpEntry.mEntry, pinnedStatus, reason);
- NotificationEntry entry = headsUpEntry.mEntry;
+ NotificationEntry entry = headsUpEntry.requireEntry();
+ mLogger.logSetEntryPinned(entry, pinnedStatus, reason);
boolean isPinned = pinnedStatus.isPinned();
if (!isPinned) {
headsUpEntry.mWasUnpinned = true;
@@ -574,7 +568,7 @@ public class HeadsUpManagerImpl
if (headsUpEntry.getPinnedStatus().getValue() != pinnedStatus) {
headsUpEntry.setRowPinnedStatus(pinnedStatus);
updatePinnedMode();
- if (isPinned && entry.getSbn() != null) {
+ if (isPinned) {
mUiEventLogger.logWithInstanceId(
NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
@@ -595,8 +589,8 @@ public class HeadsUpManagerImpl
* @param headsUpEntry entry added
*/
@VisibleForTesting
- void onEntryAdded(HeadsUpEntry headsUpEntry, PinnedStatus requestedPinnedStatus) {
- NotificationEntry entry = headsUpEntry.mEntry;
+ void onEntryAdded(HeadsUpEntry headsUpEntry, PinnedStatus requestedPinnedStatus) {
+ NotificationEntry entry = headsUpEntry.requireEntry();
entry.setHeadsUp(true);
PinnedStatus pinnedStatus = getNewPinnedStatusForEntry(headsUpEntry, requestedPinnedStatus);
@@ -635,7 +629,7 @@ public class HeadsUpManagerImpl
* Remove a notification from the alerting entries.
* @param key key of notification to remove
*/
- protected final void removeEntry(@NonNull String key, String reason) {
+ private void removeEntry(@NonNull String key, String reason) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
boolean isWaiting;
if (headsUpEntry == null) {
@@ -652,10 +646,10 @@ public class HeadsUpManagerImpl
if (finalHeadsUpEntry == null) {
return;
}
- NotificationEntry entry = finalHeadsUpEntry.mEntry;
+ NotificationEntry entry = finalHeadsUpEntry.requireEntry();
// If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
+ if (entry.isExpandAnimationRunning()) {
return;
}
entry.demoteStickyHun();
@@ -677,8 +671,9 @@ public class HeadsUpManagerImpl
* @param headsUpEntry entry removed
* @param reason why onEntryRemoved was called
*/
- protected void onEntryRemoved(HeadsUpEntry headsUpEntry, String reason) {
- NotificationEntry entry = headsUpEntry.mEntry;
+ @VisibleForTesting
+ void onEntryRemoved(@NonNull HeadsUpEntry headsUpEntry, String reason) {
+ NotificationEntry entry = headsUpEntry.requireEntry();
entry.setHeadsUp(false);
setEntryPinned(headsUpEntry, PinnedStatus.NotPinned, "onEntryRemoved");
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
@@ -700,15 +695,14 @@ public class HeadsUpManagerImpl
// mEntriesToRemoveWhenReorderingAllowed, we should not remove from this list (and cause
// ArrayIndexOutOfBoundsException). We don't need to in this case anyway, because we
// clear mEntriesToRemoveWhenReorderingAllowed after removing these entries.
- if (!reason.equals(REASON_REORDER_ALLOWED)
- && mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)) {
+ if (!reason.equals(REASON_REORDER_ALLOWED)) {
mEntriesToRemoveWhenReorderingAllowed.remove(notifEntry);
}
}
}
private void updateTopHeadsUpFlow() {
- mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
+ mTopHeadsUpRow.setValue(getTopHeadsUpEntry());
}
private void updateHeadsUpFlow() {
@@ -753,18 +747,7 @@ public class HeadsUpManagerImpl
}
}
- /**
- * Manager-specific logic, that should occur, when the entry is updated, and its posted time has
- * changed.
- *
- * @param headsUpEntry entry updated
- */
- protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
- // no need to update the list here
- updateTopHeadsUpFlow();
- }
-
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
mPinnedNotificationStatus = pinnedNotificationStatusInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
@@ -806,7 +789,7 @@ public class HeadsUpManagerImpl
keySet.addAll(mAvalancheController.getWaitingKeys());
for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
- if (entry.mEntry == null) {
+ if (entry == null || entry.mEntry == null) {
continue;
}
String packageName = entry.mEntry.getSbn().getPackageName();
@@ -828,7 +811,8 @@ public class HeadsUpManagerImpl
}
@Nullable
- protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ @VisibleForTesting
+ HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
if (mHeadsUpEntryMap.containsKey(key)) {
return mHeadsUpEntryMap.get(key);
}
@@ -845,7 +829,7 @@ public class HeadsUpManagerImpl
}
@Nullable
- protected HeadsUpEntry getTopHeadsUpEntry() {
+ private HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntryMap.isEmpty()) {
return null;
}
@@ -941,7 +925,7 @@ public class HeadsUpManagerImpl
dumpInternal(pw, args);
}
- protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
+ private void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
pw.print(" now="); pw.println(mSystemClock.elapsedRealtime());
@@ -978,7 +962,7 @@ public class HeadsUpManagerImpl
private boolean hasPinnedNotificationInternal() {
for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry entry = getHeadsUpEntry(key);
- if (entry.mEntry != null && entry.mEntry.isRowPinned()) {
+ if (entry != null && entry.mEntry != null && entry.mEntry.isRowPinned()) {
return true;
}
}
@@ -1003,6 +987,10 @@ public class HeadsUpManagerImpl
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry == null) {
+ Log.wtf(TAG, "Couldn't find entry " + key + " in unpinAll");
+ continue;
+ }
mLogger.logUnpinEntryRequest(key);
Runnable runnable = () -> {
mLogger.logUnpinEntry(key);
@@ -1013,10 +1001,10 @@ public class HeadsUpManagerImpl
// when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
// on the screen.
- if (userUnPinned && headsUpEntry.mEntry != null) {
- if (headsUpEntry.mEntry != null && headsUpEntry.mEntry.mustStayOnScreen()) {
- headsUpEntry.mEntry.setHeadsUpIsVisible();
- }
+ if (userUnPinned
+ && headsUpEntry.mEntry != null
+ && headsUpEntry.mEntry.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
}
};
mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
@@ -1037,7 +1025,7 @@ public class HeadsUpManagerImpl
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
- onEntryUpdated(headsUpEntry);
+ updateTopHeadsUpFlow();
}
}
@@ -1113,7 +1101,7 @@ public class HeadsUpManagerImpl
*
* @param entry the entry that might be indirectly removed by the user's action
*
- * @see HeadsUpCoordinator#mActionPressListener
+ * @see HeadsUpCoordinator.mActionPressListener
* @see #canRemoveImmediately(String)
*/
public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
@@ -1152,8 +1140,8 @@ public class HeadsUpManagerImpl
}
/**
- * @param key
- * @return true if the entry is (pinned and expanded) or (has an active remote input)
+ * @return true if the entry with the given key is (pinned and expanded) or (has an active
+ * remote input)
*/
@Override
public boolean isSticky(String key) {
@@ -1165,7 +1153,8 @@ public class HeadsUpManagerImpl
}
@NonNull
- protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
+ @VisibleForTesting
+ HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
if (NotificationThrottleHun.isEnabled()) {
return new HeadsUpEntry(entry);
} else {
@@ -1189,7 +1178,7 @@ public class HeadsUpManagerImpl
}
@VisibleForTesting
- public final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
+ final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
if (NotificationThrottleHun.isEnabled()) {
mAvalancheController.setEnableAtRuntime(true);
if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
@@ -1266,14 +1255,15 @@ public class HeadsUpManagerImpl
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
- protected boolean mExpanded;
- protected boolean mWasUnpinned;
+ private boolean mExpanded;
+ @VisibleForTesting
+ boolean mWasUnpinned;
@Nullable public NotificationEntry mEntry;
public long mPostTime;
public long mEarliestRemovalTime;
- @Nullable protected Runnable mRemoveRunnable;
+ @Nullable private Runnable mRemoveRunnable;
@Nullable private Runnable mCancelRemoveRunnable;
@@ -1325,7 +1315,7 @@ public class HeadsUpManagerImpl
setEntry(entry, createRemoveRunnable(entry));
}
- protected void setEntry(
+ private void setEntry(
@NonNull final NotificationEntry entry,
@Nullable Runnable removeRunnable) {
mEntry = entry;
@@ -1342,7 +1332,8 @@ public class HeadsUpManagerImpl
}
}
- protected void setRowPinnedStatus(PinnedStatus pinnedStatus) {
+ @VisibleForTesting
+ void setRowPinnedStatus(PinnedStatus pinnedStatus) {
if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
mPinnedStatus.setValue(pinnedStatus);
}
@@ -1350,7 +1341,7 @@ public class HeadsUpManagerImpl
/**
* An interface that returns the amount of time left this HUN should show.
*/
- interface FinishTimeUpdater {
+ private interface FinishTimeUpdater {
long updateAndGetTimeRemaining();
}
@@ -1370,6 +1361,10 @@ public class HeadsUpManagerImpl
public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime,
@Nullable String reason) {
Runnable runnable = () -> {
+ if (mEntry == null) {
+ Log.wtf(TAG, "#updateEntry called with null mEntry; returning early");
+ return;
+ }
mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
final long now = mSystemClock.elapsedRealtime();
@@ -1391,23 +1386,18 @@ public class HeadsUpManagerImpl
FinishTimeUpdater finishTimeCalculator = () -> {
final long finishTime = calculateFinishTime();
final long now = mSystemClock.elapsedRealtime();
- final long timeLeft = NotificationThrottleHun.isEnabled()
+ return NotificationThrottleHun.isEnabled()
? Math.max(finishTime, mEarliestRemovalTime) - now
: Math.max(finishTime - now, mMinimumDisplayTime);
- return timeLeft;
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
// Notify the manager, that the posted time has changed.
- onEntryUpdated(this);
+ updateTopHeadsUpFlow();
- if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
- mEntriesToRemoveAfterExpand.remove(mEntry);
- }
+ mEntriesToRemoveAfterExpand.remove(mEntry);
if (!NotificationThrottleHun.isEnabled()) {
- if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
- }
+ mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
}
}
@@ -1528,8 +1518,7 @@ public class HeadsUpManagerImpl
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof HeadsUpEntry)) return false;
- HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+ if (!(o instanceof HeadsUpEntry otherHeadsUpEntry)) return false;
if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
}
@@ -1593,10 +1582,13 @@ public class HeadsUpManagerImpl
}
}
- public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ private void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
@NonNull String reason) {
-
- mLogger.logAutoRemoveRequest(this.mEntry, reason);
+ if (mEntry == null) {
+ Log.wtf(TAG, "#scheduleAutoRemovalCallback with null mEntry; returning early");
+ return;
+ }
+ mLogger.logAutoRemoveRequest(mEntry, reason);
Runnable runnable = () -> {
long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
@@ -1636,16 +1628,14 @@ public class HeadsUpManagerImpl
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- FinishTimeUpdater finishTimeCalculator = () -> {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- return timeLeft;
- };
+ FinishTimeUpdater finishTimeCalculator = () ->
+ mEarliestRemovalTime - mSystemClock.elapsedRealtime();
scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
/** Creates a runnable to remove this notification from the alerting entries. */
- protected Runnable createRemoveRunnable(NotificationEntry entry) {
+ private Runnable createRemoveRunnable(NotificationEntry entry) {
return () -> {
if (!NotificationThrottleHun.isEnabled()
&& !mVisualStabilityProvider.isReorderingAllowed()
@@ -1669,7 +1659,7 @@ public class HeadsUpManagerImpl
* Calculate what the post time of a notification is at some current time.
* @return the post time
*/
- protected long calculatePostTime() {
+ private long calculatePostTime() {
// The actual post time will be just after the heads-up really slided in
return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
}
@@ -1678,7 +1668,7 @@ public class HeadsUpManagerImpl
* @return When the notification should auto-dismiss itself, based on
* {@link SystemClock#elapsedRealtime()}
*/
- protected long calculateFinishTime() {
+ private long calculateFinishTime() {
int requestedTimeOutMs;
if (isStickyForSomeTime()) {
requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
@@ -1692,9 +1682,8 @@ public class HeadsUpManagerImpl
/**
* Get user-preferred or default timeout duration. The larger one will be returned.
* @return milliseconds before auto-dismiss
- * @param requestedTimeout
*/
- protected int getRecommendedHeadsUpTimeoutMs(int requestedTimeout) {
+ private int getRecommendedHeadsUpTimeoutMs(int requestedTimeout) {
return mAccessibilityMgr.getRecommendedTimeoutMillis(
requestedTimeout,
AccessibilityManager.FLAG_CONTENT_CONTROLS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
index 1ccc45b9c385..e3ca7c81582f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -156,12 +156,12 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
)
}
- fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
+ fun logAutoRemoveCanceled(entry: NotificationEntry?, reason: String?) {
buffer.log(
TAG,
INFO,
{
- str1 = entry.logKey
+ str1 = entry?.logKey
str2 = reason ?: "unknown"
},
{ "cancel auto remove of $str1 reason: $str2" },
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
index fc432ba973ab..839028e4c3b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
@@ -16,62 +16,9 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.app.tracing.traceSection
-import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
-
-/** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */
-class NotificationIconContainerAlwaysOnDisplayViewBinder
-@Inject
-constructor(
- private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
- @ShadeDisplayAware private val configuration: ConfigurationState,
- private val failureTracker: StatusBarIconViewBindingFailureTracker,
- private val screenOffAnimationController: ScreenOffAnimationController,
- private val systemBarUtilsState: SystemBarUtilsState,
- private val viewStore: AlwaysOnDisplayNotificationIconViewStore,
-) {
- fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
- return traceSection("NICAlwaysOnDisplay#bindWhileAttached") {
- view.repeatWhenAttached {
- lifecycleScope.launch {
- launch {
- NotificationIconContainerViewBinder.bind(
- view = view,
- viewModel = viewModel,
- configuration = configuration,
- systemBarUtilsState = systemBarUtilsState,
- failureTracker = failureTracker,
- viewStore = viewStore,
- )
- }
- launch {
- KeyguardRootViewBinder.bindAodNotifIconVisibility(
- view = view,
- isVisible = keyguardRootViewModel.isNotifIconContainerVisible,
- configuration = configuration,
- screenOffAnimationController = screenOffAnimationController,
- )
- }
- }
- }
- }
- }
-}
/** [IconViewStore] for the always-on display. */
class AlwaysOnDisplayNotificationIconViewStore
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 072089981cc7..7c9d850eaf07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1284,7 +1284,11 @@ public class NotificationStackScrollLayout
@Override
public void setStackCutoff(float stackCutoff) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- mAmbientState.setStackCutoff(stackCutoff);
+ if (mAmbientState.getStackCutoff() != stackCutoff) {
+ mAmbientState.setStackCutoff(stackCutoff);
+ updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ requestChildrenUpdate();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index c5bef99f9307..ef68b4de5291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -109,11 +109,6 @@ constructor(
}
}
launch {
- viewModel.shouldResetStackTop
- .filter { it }
- .collectTraced { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
- }
- launch {
viewModel.shouldCloseGuts
.filter { it }
.collectTraced { view.closeGutsOnSceneTouch() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 56b335648138..1bb205cbcb61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -192,12 +192,6 @@ constructor(
/** Whether we should close any open notification guts. */
val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts
- val shouldResetStackTop: Flow<Boolean> =
- sceneInteractor.transitionState
- .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone }
- .distinctUntilChanged()
- .dumpWhileCollecting("shouldResetStackTop")
-
/** Whether the Notification Stack is visibly on the lockscreen scene. */
val isShowingStackOnLockscreen: Flow<Boolean> =
sceneInteractor.transitionState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index fe5a02be2fb3..153dd990820d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -67,6 +67,7 @@ class ComponentSystemUIDialog(
broadcastDispatcher,
dialogTransitionAnimator,
delegate,
+ true, /* shouldAcsdDismissDialog */
),
LifecycleOwner,
SavedStateRegistryOwner,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 12684fa8518a..0fac6448909c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -316,7 +316,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
.isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
boolean userPublic = devicePublic
|| mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+ boolean needsRedaction = mLockscreenUserManager.getRedactionType(entry)
+ != NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
if (userPublic && needsRedaction) {
// TODO(b/135046837): we can probably relax this with dynamic privacy
return true;
@@ -369,7 +370,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
return false;
}
- if (!mLockscreenUserManager.needsRedaction(entry)) {
+ if (mLockscreenUserManager.getRedactionType(entry)
+ == NotificationLockscreenUserManager.REDACTION_TYPE_NONE) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 0ad1042a665f..03324d2a3e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -145,7 +145,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
*/
public SystemUIDialog create() {
return create(new DialogDelegate<>() {
- }, mContext, DEFAULT_THEME);
+ }, mContext, DEFAULT_THEME, true /* shouldAcsdDismissDialog */);
}
/**
@@ -155,7 +155,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
*/
public SystemUIDialog create(Context context) {
return create(new DialogDelegate<>() {
- }, context, DEFAULT_THEME);
+ }, context, DEFAULT_THEME, true /* shouldAcsdDismissDialog */);
}
/**
@@ -168,8 +168,21 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
return create(delegate, context, DEFAULT_THEME);
}
+ /**
+ * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link
+ * Delegate}. When you need to customize the dialog, pass it a delegate.
+ *
+ * This method allows the caller to specify if the dialog should be dismissed in response
+ * to the ACTION_CLOSE_SYSTEM_DIALOGS intent.
+ */
+ public SystemUIDialog create(Delegate delegate, Context context,
+ boolean shouldAcsdDismissDialog) {
+ return create(delegate, context, DEFAULT_THEME, shouldAcsdDismissDialog);
+ }
+
public SystemUIDialog create(Delegate delegate, Context context, @StyleRes int theme) {
- return create((DialogDelegate<SystemUIDialog>) delegate, context, theme);
+ return create((DialogDelegate<SystemUIDialog>) delegate, context, theme,
+ true /* shouldAcsdDismissDialog */);
}
public SystemUIDialog create(Delegate delegate) {
@@ -177,7 +190,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate,
- Context context, @StyleRes int theme) {
+ Context context, @StyleRes int theme, boolean shouldAcsdDismissDialog) {
return new SystemUIDialog(
context,
theme,
@@ -186,7 +199,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
mSysUiState,
mBroadcastDispatcher,
mDialogTransitionAnimator,
- dialogDelegate);
+ dialogDelegate,
+ shouldAcsdDismissDialog);
}
}
@@ -207,7 +221,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
broadcastDispatcher,
dialogTransitionAnimator,
new DialogDelegate<>() {
- });
+ },
+ true /* shouldAcsdDismissDialog */);
}
public SystemUIDialog(
@@ -227,7 +242,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
sysUiState,
broadcastDispatcher,
dialogTransitionAnimator,
- (DialogDelegate<SystemUIDialog>) delegate);
+ (DialogDelegate<SystemUIDialog>) delegate,
+ true /* shouldAcsdDismissDialog */);
}
public SystemUIDialog(
@@ -238,7 +254,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogTransitionAnimator dialogTransitionAnimator,
- DialogDelegate<SystemUIDialog> delegate) {
+ DialogDelegate<SystemUIDialog> delegate,
+ boolean shouldAcsdDismissDialog) {
super(context, theme);
mContext = context;
mDelegate = delegate;
@@ -249,7 +266,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
getWindow().setAttributes(attrs);
mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher,
- dialogTransitionAnimator) : null;
+ dialogTransitionAnimator, shouldAcsdDismissDialog) : null;
mDialogManager = dialogManager;
mSysUiState = sysUiState;
}
@@ -523,7 +540,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
// TODO(b/219008720): Remove those calls to Dependency.get.
DismissReceiver dismissReceiver = new DismissReceiver(dialog,
Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogTransitionAnimator.class));
+ Dependency.get(DialogTransitionAnimator.class),
+ true /* shouldAcsdDismissDialog */);
dialog.setOnDismissListener(d -> {
dismissReceiver.unregister();
if (dismissAction != null) dismissAction.run();
@@ -595,12 +613,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
private static class DismissReceiver extends BroadcastReceiver {
- private static final IntentFilter INTENT_FILTER = new IntentFilter();
-
- static {
- INTENT_FILTER.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
- }
+ private final IntentFilter mIntentFilter = new IntentFilter();
private final Dialog mDialog;
private boolean mRegistered;
@@ -608,14 +621,20 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final DialogTransitionAnimator mDialogTransitionAnimator;
DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher,
- DialogTransitionAnimator dialogTransitionAnimator) {
+ DialogTransitionAnimator dialogTransitionAnimator,
+ boolean shouldAcsdDismissDialog) {
mDialog = dialog;
mBroadcastDispatcher = broadcastDispatcher;
mDialogTransitionAnimator = dialogTransitionAnimator;
+
+ mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ if (shouldAcsdDismissDialog) {
+ mIntentFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ }
}
void register() {
- mBroadcastDispatcher.registerReceiver(this, INTENT_FILTER, null, UserHandle.CURRENT);
+ mBroadcastDispatcher.registerReceiver(this, mIntentFilter, null, UserHandle.CURRENT);
mRegistered = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index b7ccfa01c92c..2f7b24393ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -16,12 +16,15 @@
package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
import com.android.systemui.activity.data.repository.ActivityManagerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
@@ -31,11 +34,15 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -55,12 +62,19 @@ class OngoingCallInteractor @Inject constructor(
private val activityManagerRepository: ActivityManagerRepository,
private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
activeNotificationsInteractor: ActiveNotificationsInteractor,
@OngoingCallLog private val logBuffer: LogBuffer,
-) {
+) : CoreStartable {
private val logger = Logger(logBuffer, TAG)
/**
+ * Tracks whether the call chip has been swiped away.
+ */
+ private val _isChipSwipedAway = MutableStateFlow(false)
+ val isChipSwipedAway: StateFlow<Boolean> = _isChipSwipedAway.asStateFlow()
+
+ /**
* The current state of ongoing calls.
*/
val ongoingCallState: StateFlow<OngoingCallModel> =
@@ -70,15 +84,29 @@ class OngoingCallInteractor @Inject constructor(
notification = notification
)
}
- .onEach { state ->
- setStatusBarRequiredForOngoingCall(state)
- }
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
initialValue = OngoingCallModel.NoCall
)
+ @VisibleForTesting
+ val isStatusBarRequiredForOngoingCall = combine(
+ ongoingCallState,
+ isChipSwipedAway
+ ) { callState, chipSwipedAway ->
+ callState is OngoingCallModel.InCall && !chipSwipedAway
+ }
+
+ @VisibleForTesting
+ val isGestureListeningEnabled = combine(
+ ongoingCallState,
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode,
+ isChipSwipedAway
+ ) { callState, isFullscreen, chipSwipedAway ->
+ callState is OngoingCallModel.InCall && !chipSwipedAway && isFullscreen
+ }
+
private fun createOngoingCallStateFlow(
notification: ActiveNotificationModel?
): Flow<OngoingCallModel> {
@@ -99,6 +127,31 @@ class OngoingCallInteractor @Inject constructor(
}
}
+ override fun start() {
+ ongoingCallState
+ .filterIsInstance<OngoingCallModel.NoCall>()
+ .onEach {
+ _isChipSwipedAway.value = false
+ }.launchIn(scope)
+
+ isStatusBarRequiredForOngoingCall.onEach { statusBarRequired ->
+ setStatusBarRequiredForOngoingCall(statusBarRequired)
+ }.launchIn(scope)
+
+ isGestureListeningEnabled.onEach { isEnabled ->
+ updateGestureListening(isEnabled)
+ }.launchIn(scope)
+ }
+
+ /**
+ * Callback that must run when the status bar is swiped while gesture listening is active.
+ */
+ @VisibleForTesting
+ fun onStatusBarSwiped() {
+ logger.d("Status bar chip swiped away")
+ _isChipSwipedAway.value = true
+ }
+
private fun deriveOngoingCallState(
model: ActiveNotificationModel,
isVisible: Boolean
@@ -126,8 +179,7 @@ class OngoingCallInteractor @Inject constructor(
}
}
- private fun setStatusBarRequiredForOngoingCall(state: OngoingCallModel) {
- val statusBarRequired = state is OngoingCallModel.InCall
+ private fun setStatusBarRequiredForOngoingCall(statusBarRequired: Boolean) {
// TODO(b/382808183): Create a single repository that can be utilized in
// `statusBarModeRepositoryStore` and `statusBarWindowControllerStore` so we do not need
// two separate calls to force the status bar to stay visible.
@@ -138,6 +190,16 @@ class OngoingCallInteractor @Inject constructor(
.setOngoingProcessRequiresStatusBarVisible(statusBarRequired)
}
+ private fun updateGestureListening(isEnabled: Boolean) {
+ if (isEnabled) {
+ swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ ->
+ onStatusBarSwiped()
+ }
+ } else {
+ swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
companion object {
private val TAG = "OngoingCall"
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index bfdae626b5e8..e1cc11a7cfd1 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -45,6 +45,8 @@ fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Uni
bodyResId = R.string.touchpad_back_gesture_guidance,
titleSuccessResId = R.string.touchpad_back_gesture_success_title,
bodySuccessResId = R.string.touchpad_back_gesture_success_body,
+ titleErrorResId = R.string.gesture_error_title,
+ bodyErrorResId = R.string.touchpad_back_gesture_error_body,
),
animations = TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_back_edu),
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 2332c0051c69..ed84f9c42c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -54,6 +54,8 @@ sealed interface GestureUiState {
val progressStartMarker: String,
val progressEndMarker: String,
) : GestureUiState
+
+ data object Error : GestureUiState
}
fun GestureState.toGestureUiState(
@@ -66,19 +68,31 @@ fun GestureState.toGestureUiState(
is GestureState.InProgress ->
GestureUiState.InProgress(this.progress, progressStartMarker, progressEndMarker)
is GestureState.Finished -> GestureUiState.Finished(successAnimation)
+ GestureState.Error -> GestureUiState.Error
}
}
-fun GestureUiState.toTutorialActionState(): TutorialActionState {
+fun GestureUiState.toTutorialActionState(previousState: TutorialActionState): TutorialActionState {
return when (this) {
NotStarted -> TutorialActionState.NotStarted
- is GestureUiState.InProgress ->
- TutorialActionState.InProgress(
- progress = progress,
- startMarker = progressStartMarker,
- endMarker = progressEndMarker,
- )
+ is GestureUiState.InProgress -> {
+ val inProgress =
+ TutorialActionState.InProgress(
+ progress = progress,
+ startMarker = progressStartMarker,
+ endMarker = progressEndMarker,
+ )
+ if (
+ previousState is TutorialActionState.InProgressAfterError ||
+ previousState is TutorialActionState.Error
+ ) {
+ return TutorialActionState.InProgressAfterError(inProgress)
+ } else {
+ return inProgress
+ }
+ }
is Finished -> TutorialActionState.Finished(successAnimation)
+ GestureUiState.Error -> TutorialActionState.Error
}
}
@@ -102,11 +116,11 @@ fun GestureTutorialScreen(
easterEggTriggered,
resetEasterEggFlag = { easterEggTriggered = false },
) {
- ActionTutorialContent(
- gestureState.toTutorialActionState(),
- onDoneButtonClicked,
- screenConfig,
- )
+ var lastState: TutorialActionState by remember {
+ mutableStateOf(TutorialActionState.NotStarted)
+ }
+ lastState = gestureState.toTutorialActionState(lastState)
+ ActionTutorialContent(lastState, onDoneButtonClicked, screenConfig)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 45fdd21e795e..26604ca6b845 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -42,6 +42,8 @@ fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Uni
bodyResId = R.string.touchpad_home_gesture_guidance,
titleSuccessResId = R.string.touchpad_home_gesture_success_title,
bodySuccessResId = R.string.touchpad_home_gesture_success_body,
+ titleErrorResId = R.string.gesture_error_title,
+ bodyErrorResId = R.string.touchpad_home_gesture_error_body,
),
animations = TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_home_edu),
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 680b670f4d97..6400aca57693 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -42,6 +42,8 @@ fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: ()
bodyResId = R.string.touchpad_recent_apps_gesture_guidance,
titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title,
bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body,
+ titleErrorResId = R.string.gesture_error_title,
+ bodyErrorResId = R.string.touchpad_recent_gesture_error_body,
),
animations =
TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_recent_apps_edu),
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
index f27ddb515c24..7bc7e81ceb51 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
@@ -23,6 +23,8 @@ sealed interface GestureState {
data class InProgress(val progress: Float = 0f, val direction: GestureDirection? = null) :
GestureState
+
+ data object Error : GestureState
}
enum class GestureDirection {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
index 24f5d1f00794..69886e4a1242 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -28,7 +28,7 @@ inline fun updateGestureState(
if (isFinished(gestureState)) {
gestureStateChangedCallback(GestureState.Finished)
} else {
- gestureStateChangedCallback(GestureState.NotStarted)
+ gestureStateChangedCallback(GestureState.Error)
}
}
is Moving -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f5616d45a3da..7fb74b3439bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -51,11 +51,11 @@ class ControlsFavoritingActivityTest : SysuiTestCase() {
private companion object {
val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName")
+ val TEST_STRUCTURE: CharSequence = "TestStructure"
val TEST_CONTROL =
mock(Control::class.java, Answers.RETURNS_MOCKS)!!.apply {
whenever(structure).thenReturn(TEST_STRUCTURE)
}
- val TEST_STRUCTURE: CharSequence = "TestStructure"
val TEST_APP: CharSequence = "TestApp"
private fun View.waitForPost() {
@@ -158,7 +158,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() {
assertThat(getBooleanExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, false))
.isTrue()
assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_STRUCTURE))
- .isEqualTo("")
+ .isEqualTo(TEST_STRUCTURE)
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 68a5d9361046..9543032ef5ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -247,7 +247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
mContext,
0,
intent.setPackage(mContext.packageName),
- PendingIntent.FLAG_MUTABLE
+ PendingIntent.FLAG_MUTABLE,
)
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -294,7 +294,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
override fun loadAnimator(
animId: Int,
otionInterpolator: Interpolator,
- vararg targets: View
+ vararg targets: View,
): AnimatorSet {
return mockAnimator
}
@@ -323,7 +323,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
packageName = PACKAGE,
instanceId = instanceId,
recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
- cardAction = smartspaceAction
+ cardAction = smartspaceAction,
)
}
@@ -370,7 +370,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
packageName = PACKAGE,
token = session.sessionToken,
device = device,
- instanceId = instanceId
+ instanceId = instanceId,
)
}
@@ -416,7 +416,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
action1.id,
action2.id,
action3.id,
- action4.id
+ action4.id,
)
}
@@ -536,7 +536,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
custom0 = MediaAction(icon, null, "custom 0", bg),
- custom1 = MediaAction(icon, null, "custom 1", bg)
+ custom1 = MediaAction(icon, null, "custom 1", bg),
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -590,7 +590,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
custom0 = MediaAction(icon, null, "custom 0", bg),
custom1 = MediaAction(icon, null, "custom 1", bg),
false,
- true
+ true,
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -622,7 +622,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
custom0 = MediaAction(icon, null, "custom 0", bg),
custom1 = MediaAction(icon, null, "custom 1", bg),
true,
- false
+ false,
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -760,7 +760,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions =
MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play", null),
- nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
+ nextOrCustom = MediaAction(icon, Runnable {}, "next", null),
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -850,7 +850,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions =
MediaButton(
prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
+ nextOrCustom = MediaAction(icon, {}, "next", null),
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -921,7 +921,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions =
MediaButton(
prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
+ nextOrCustom = MediaAction(icon, {}, "next", null),
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -944,7 +944,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions =
MediaButton(
prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
+ nextOrCustom = MediaAction(icon, {}, "next", null),
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -966,6 +966,29 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun setIsScrubbing_reservedButtonSpaces_scrubbingTimesShown() {
+ val semanticActions =
+ MediaButton(
+ prevOrCustom = null,
+ nextOrCustom = null,
+ reserveNext = true,
+ reservePrev = true,
+ )
+ val state = mediaData.copy(semanticActions = semanticActions)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ reset(expandedSet)
+
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ verify(expandedSet).setVisibility(R.id.actionPrev, View.GONE)
+ verify(expandedSet).setVisibility(R.id.actionNext, View.GONE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_elapsed_time, View.VISIBLE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_total_time, View.VISIBLE)
+ }
+
+ @Test
fun bind_resumeState_withProgress() {
val progress = 0.5
val state = mediaData.copy(resumption = true, resumeProgress = progress)
@@ -1009,13 +1032,13 @@ public class MediaControlPanelTest : SysuiTestCase() {
MediaNotificationAction(true, actionIntent = pendingIntent, icon, "play"),
MediaNotificationAction(true, actionIntent = null, icon, "next"),
MediaNotificationAction(true, actionIntent = null, icon, "custom 0"),
- MediaNotificationAction(true, actionIntent = pendingIntent, icon, "custom 1")
+ MediaNotificationAction(true, actionIntent = pendingIntent, icon, "custom 1"),
)
val state =
mediaData.copy(
actions = actions,
actionsToShowInCompact = listOf(1, 2),
- semanticActions = null
+ semanticActions = null,
)
player.attachPlayer(viewHolder)
@@ -1701,7 +1724,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"),
- MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4")
+ MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4"),
)
val data = mediaData.copy(actions = actions)
@@ -1720,7 +1743,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"),
- MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4")
+ MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4"),
)
val data = mediaData.copy(actions = actions)
@@ -1739,7 +1762,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 1"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 2"),
MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 3"),
- MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4")
+ MediaNotificationAction(true, actionIntent = pendingIntent, null, "action 4"),
)
val data = mediaData.copy(actions = actions)
@@ -2021,7 +2044,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle(subtitle3)
.setIcon(icon)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
player.bindRecommendation(data)
@@ -2047,7 +2070,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setIcon(
Icon.createWithResource(
context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata
+ com.android.settingslib.R.drawable.ic_1x_mobiledata,
)
)
.setExtras(Bundle.EMPTY)
@@ -2084,7 +2107,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("fake subtitle")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
player.bindRecommendation(data)
@@ -2119,7 +2142,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
player.bindRecommendation(data)
@@ -2142,7 +2165,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setIcon(
Icon.createWithResource(
context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata
+ com.android.settingslib.R.drawable.ic_1x_mobiledata,
)
)
.setExtras(Bundle.EMPTY)
@@ -2157,11 +2180,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setIcon(
Icon.createWithResource(
context,
- com.android.settingslib.R.drawable.ic_3g_mobiledata
+ com.android.settingslib.R.drawable.ic_3g_mobiledata,
)
)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2185,7 +2208,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setIcon(
Icon.createWithResource(
context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata
+ com.android.settingslib.R.drawable.ic_1x_mobiledata,
)
)
.setExtras(Bundle.EMPTY)
@@ -2200,11 +2223,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setIcon(
Icon.createWithResource(
context,
- com.android.settingslib.R.drawable.ic_3g_mobiledata
+ com.android.settingslib.R.drawable.ic_3g_mobiledata,
)
)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2245,7 +2268,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("subtitle1")
.setIcon(albumArt)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2268,7 +2291,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
Bundle().apply {
putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
)
putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
}
@@ -2290,7 +2313,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("subtitle1")
.setIcon(albumArt)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2328,7 +2351,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("subtitle1")
.setIcon(albumArt)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2381,7 +2404,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.setSubtitle("subtitle1")
.setIcon(albumArt)
.setExtras(Bundle.EMPTY)
- .build()
+ .build(),
)
)
@@ -2444,7 +2467,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
icon = null,
action = {},
contentDescription = "play",
- background = null
+ background = null,
)
)
val data = mediaData.copy(semanticActions = semanticActions)
@@ -2465,7 +2488,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
icon = null,
action = {},
contentDescription = "play",
- background = null
+ background = null,
)
)
val data = mediaData.copy(semanticActions = semanticActions)
@@ -2498,7 +2521,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
icon = null,
action = {},
contentDescription = "play",
- background = null
+ background = null,
)
)
val data = mediaData.copy(semanticActions = semanticActions)
@@ -2530,8 +2553,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
icon = null,
action = {},
contentDescription = "custom0",
- background = null
- ),
+ background = null,
+ )
)
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -2553,8 +2576,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
icon = null,
action = {},
contentDescription = "custom0",
- background = null
- ),
+ background = null,
+ )
)
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 90ffaf19be96..67c5986fe5d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -236,7 +236,7 @@ class QSTileViewImplTest : SysuiTestCase() {
context.orCreateTestableResources.addOverride(
R.array.tile_states_internet,
- arrayOf(unavailableString, offString, onString)
+ arrayOf(unavailableString, offString, onString),
)
// State UNAVAILABLE
@@ -341,7 +341,7 @@ class QSTileViewImplTest : SysuiTestCase() {
val testA11yLabel = "TEST_LABEL"
context.orCreateTestableResources.addOverride(
R.string.accessibility_tile_disabled_by_policy_action_description,
- testA11yLabel
+ testA11yLabel,
)
val stateDisabledByPolicy = QSTile.State()
@@ -374,7 +374,7 @@ class QSTileViewImplTest : SysuiTestCase() {
context.orCreateTestableResources.addOverride(
R.array.tile_states_internet,
- arrayOf(unavailableString, offString, onString)
+ arrayOf(unavailableString, offString, onString),
)
tileView.changeState(state)
@@ -477,6 +477,24 @@ class QSTileViewImplTest : SysuiTestCase() {
}
@Test
+ fun onStateChange_fromLongPress_toNoLongPress_whileLongPressRuns_doesNotClearResources() {
+ // GIVEN that the long-press effect has been initialized
+ val state = QSTile.State()
+ state.handlesLongClick = true
+ tileView.changeState(state)
+
+ // WHEN the long-press effect is running
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.RUNNING_FORWARD)
+
+ // WHEN a state changed happens so that the tile no longer handles long-press
+ state.handlesLongClick = false
+ tileView.changeState(state)
+
+ // THEN the long-press effect resources are not cleared
+ assertThat(tileView.areLongPressEffectPropertiesSet).isTrue()
+ }
+
+ @Test
fun onStateChange_withoutLongPressEffect_fromLongPress_to_noLongPress_neverSetsProperties() {
// GIVEN a tile where the long-press effect is null
tileView = FakeTileView(context, false, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 99467cb11282..a91fb45c9c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -95,6 +95,7 @@ import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShade
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
@@ -103,7 +104,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
@@ -356,6 +356,31 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@EnableSceneContainer
+ public void updateStackCutoff_updatesStackEndHeight() {
+ // GIVEN shade is fully open
+ final float stackTop = 200f;
+ final float stackCutoff = 1000f;
+ final float stackHeight = stackCutoff - stackTop;
+ mAmbientState.setStackTop(stackTop);
+ mAmbientState.setStackCutoff(stackCutoff);
+ mAmbientState.setStatusBarState(StatusBarState.SHADE);
+ mStackScroller.setMaxDisplayedNotifications(-1); // no limit on the shade
+ mStackScroller.setExpandFraction(1f); // shade is fully expanded
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(stackHeight);
+
+ // WHEN stackCutoff changes
+ final float newStackCutoff = 800;
+ mStackScroller.setStackCutoff(newStackCutoff);
+
+ // THEN stackEndHeight is updated
+ final float newStackHeight = newStackCutoff - stackTop;
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(newStackHeight);
+ assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(newStackHeight);
+ }
+
+ @Test
+ @EnableSceneContainer
public void updateStackEndHeightAndStackHeight_maxNotificationsSet_withSceneContainer() {
float stackHeight = 300f;
when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
index 5d146fbffaca..3d45a51b9f3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -29,7 +29,7 @@ import com.android.systemui.util.time.systemClock
/** This factory creates empty mocks. */
var Kosmos.brightnessSliderControllerFactory by
Kosmos.Fixture<BrightnessSliderController.Factory> {
- BrightnessSliderController.Factory(
+ BrightnessSliderController.BrightnessSliderControllerFactory(
falsingManager,
uiEventLogger,
vibratorHelper,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 6944e6c14096..ab193d294b8c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -261,7 +261,7 @@ class ShadeTestUtilSceneImpl(
}
private fun setIdleScene(scene: SceneKey) {
- sceneInteractor.changeScene(scene, "test")
+ sceneInteractor.changeScene(scene, "ShadeTestUtil.setIdleScene")
val transitionState =
MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(scene))
sceneInteractor.setTransitionState(transitionState)
@@ -274,7 +274,7 @@ class ShadeTestUtilSceneImpl(
progress: Float,
isInitiatedByUserInput: Boolean = true,
) {
- sceneInteractor.changeScene(from, "test")
+ sceneInteractor.changeScene(from, "ShadeTestUtil.setTransitionProgress")
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Transition(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt
new file mode 100644
index 000000000000..72165c95fc55
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler by
+Kosmos.Fixture {
+ mock<SwipeStatusBarAwayGestureHandler>()
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
index 9090e02b22b6..40d91017eeef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
@@ -32,6 +33,7 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by
activityManagerRepository = activityManagerRepository,
statusBarModeRepositoryStore = fakeStatusBarModeRepository,
statusBarWindowControllerStore = fakeStatusBarWindowControllerStore,
+ swipeStatusBarAwayGestureHandler = swipeStatusBarAwayGestureHandler,
logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"),
)
}
diff --git a/services/Android.bp b/services/Android.bp
index 473911f08cf7..3d512f782232 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -233,8 +233,7 @@ ondeviceintelligence_module_java_defaults {
libs: ["service-ondeviceintelligence.stubs.system_server"],
},
release_ondevice_intelligence_platform: {
- srcs: [":service-ondeviceintelligence-sources"],
- static_libs: ["modules-utils-backgroundthread"],
+ srcs: [":service-ondeviceintelligence-sources-platform"],
},
},
}
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 65c446ee6fa8..7c5cfa91ab8a 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -96,13 +96,3 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- name: "add_accessibility_title_for_augmented_autofill_dropdown"
- namespace: "autofill"
- description: "Add accessibility title for augmented autofill dropdown"
- bug: "375284244"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 36dff89d9d61..778c6864282d 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -228,20 +228,31 @@ public class BinaryTransparencyService extends SystemService {
computePackageSignerSha256Digests(packageState.getSigningInfo());
AndroidPackage pkg = packageState.getAndroidPackage();
- for (AndroidPackageSplit split : pkg.getSplits()) {
+ if(pkg != null) {
+ for (AndroidPackageSplit split : pkg.getSplits()) {
+ var appInfo = new IBinaryTransparencyService.AppInfo();
+ appInfo.packageName = packageName;
+ appInfo.longVersion = versionCode;
+ appInfo.splitName = split.getName(); // base's split name is null
+ // Signer digests are consistent between splits, guaranteed by Package Manager.
+ appInfo.signerDigests = signerDigests;
+ appInfo.mbaStatus = mbaStatus;
+
+ // Only digest and split name are different between splits.
+ Digest digest = measureApk(split.getPath());
+ appInfo.digest = digest.value();
+ appInfo.digestAlgorithm = digest.algorithm();
+
+ results.add(appInfo);
+ }
+ } else {
+ Slog.w(TAG, packageName + " APK file is not physically present,"
+ + " skipping split and digest measurement");
var appInfo = new IBinaryTransparencyService.AppInfo();
appInfo.packageName = packageName;
appInfo.longVersion = versionCode;
- appInfo.splitName = split.getName(); // base's split name is null
- // Signer digests are consistent between splits, guaranteed by Package Manager.
appInfo.signerDigests = signerDigests;
appInfo.mbaStatus = mbaStatus;
-
- // Only digest and split name are different between splits.
- Digest digest = measureApk(split.getPath());
- appInfo.digest = digest.value();
- appInfo.digestAlgorithm = digest.algorithm();
-
results.add(appInfo);
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index ccc44a41759b..bedc1308e8eb 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -16,9 +16,13 @@
package com.android.server;
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -33,6 +37,7 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
+import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -41,10 +46,12 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
@@ -70,7 +77,7 @@ public class GestureLauncherService extends SystemService {
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
- @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
+ @VisibleForTesting static final long POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
/**
@@ -100,10 +107,23 @@ public class GestureLauncherService extends SystemService {
@VisibleForTesting
static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
- /**
- * Number of taps required to launch camera shortcut.
- */
- private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
+ /** Indicates camera should be launched on power double tap. */
+ @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
+
+ /** Indicates wallet should be launched on power double tap. */
+ @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
+
+ /** Number of taps required to launch the double tap shortcut (either camera or wallet). */
+ public static final int DOUBLE_POWER_TAP_COUNT_THRESHOLD = 2;
+
+ /** Bundle to send with PendingIntent to grant background activity start privileges. */
+ private static final Bundle GRANT_BACKGROUND_START_PRIVILEGES =
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
+ .toBundle();
+
+ private final QuickAccessWalletClient mQuickAccessWalletClient;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
@@ -158,6 +178,9 @@ public class GestureLauncherService extends SystemService {
*/
private boolean mCameraDoubleTapPowerEnabled;
+ /** Whether wallet double tap power button gesture is currently enabled. */
+ private boolean mWalletDoubleTapPowerEnabled;
+
/**
* Whether emergency gesture is currently enabled
*/
@@ -204,15 +227,17 @@ public class GestureLauncherService extends SystemService {
}
}
public GestureLauncherService(Context context) {
- this(context, new MetricsLogger(), new UiEventLoggerImpl());
+ this(context, new MetricsLogger(),
+ QuickAccessWalletClient.create(context), new UiEventLoggerImpl());
}
@VisibleForTesting
GestureLauncherService(Context context, MetricsLogger metricsLogger,
- UiEventLogger uiEventLogger) {
+ QuickAccessWalletClient quickAccessWalletClient, UiEventLogger uiEventLogger) {
super(context);
mContext = context;
mMetricsLogger = metricsLogger;
+ mQuickAccessWalletClient = quickAccessWalletClient;
mUiEventLogger = uiEventLogger;
}
@@ -237,6 +262,9 @@ public class GestureLauncherService extends SystemService {
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ updateWalletDoubleTapPowerEnabled();
+ }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
@@ -250,12 +278,24 @@ public class GestureLauncherService extends SystemService {
}
private void registerContentObservers() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
- false, mSettingObserver, mUserId);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
- false, mSettingObserver, mUserId);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED),
+ false, mSettingObserver, mUserId);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE),
+ false, mSettingObserver, mUserId);
+ } else {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
+ false, mSettingObserver, mUserId);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
+ false, mSettingObserver, mUserId);
+ }
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
@@ -292,6 +332,14 @@ public class GestureLauncherService extends SystemService {
}
@VisibleForTesting
+ void updateWalletDoubleTapPowerEnabled() {
+ boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
+ synchronized (this) {
+ mWalletDoubleTapPowerEnabled = enabled;
+ }
+ }
+
+ @VisibleForTesting
void updateEmergencyGestureEnabled() {
boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
synchronized (this) {
@@ -418,10 +466,34 @@ public class GestureLauncherService extends SystemService {
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
+
+ /** Checks if camera should be launched on double press of the power button. */
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
- return isCameraDoubleTapPowerEnabled(context.getResources())
- && (Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
+ boolean res;
+
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ res = isDoubleTapPowerGestureSettingEnabled(context, userId)
+ && getDoubleTapPowerGestureAction(context, userId)
+ == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
+ } else {
+ // These are legacy settings that will be deprecated once the option to launch both
+ // wallet and camera has been created.
+ res = isCameraDoubleTapPowerEnabled(context.getResources())
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
+ }
+ return res;
+ }
+
+ /** Checks if wallet should be launched on double tap of the power button. */
+ public static boolean isWalletDoubleTapPowerSettingEnabled(Context context, int userId) {
+ if (!launchWalletOptionOnPowerDoubleTap()) {
+ return false;
+ }
+
+ return isDoubleTapPowerGestureSettingEnabled(context, userId)
+ && getDoubleTapPowerGestureAction(context, userId)
+ == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
}
public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
@@ -441,6 +513,28 @@ public class GestureLauncherService extends SystemService {
isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
+ private static int getDoubleTapPowerGestureAction(Context context, int userId) {
+ return Settings.Secure.getIntForUser(
+ context.getContentResolver(),
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+ LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
+ userId);
+ }
+
+ /** Whether the shortcut to launch app on power double press is enabled. */
+ private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
+ return Settings.Secure.getIntForUser(
+ context.getContentResolver(),
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+ isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
+ userId)
+ == 1;
+ }
+
+ private static boolean isDoubleTapConfigEnabled(Resources resources) {
+ return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
+ }
+
/**
* Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
* value is capped at a maximum
@@ -494,12 +588,19 @@ public class GestureLauncherService extends SystemService {
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
- return isCameraLaunchEnabled(resources)
- || isCameraDoubleTapPowerEnabled(resources)
- || isCameraLiftTriggerEnabled(resources)
- || isEmergencyGestureEnabled(resources);
+ boolean res =
+ isCameraLaunchEnabled(resources)
+ || isCameraLiftTriggerEnabled(resources)
+ || isEmergencyGestureEnabled(resources);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ res |= isDoubleTapConfigEnabled(resources);
+ } else {
+ res |= isCameraDoubleTapPowerEnabled(resources);
+ }
+ return res;
}
+
/**
* Attempts to intercept power key down event by detecting certain gesture patterns
*
@@ -530,6 +631,7 @@ public class GestureLauncherService extends SystemService {
return false;
}
boolean launchCamera = false;
+ boolean launchWallet = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
@@ -541,7 +643,7 @@ public class GestureLauncherService extends SystemService {
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
+ } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
@@ -586,10 +688,16 @@ public class GestureLauncherService extends SystemService {
}
}
if (mCameraDoubleTapPowerEnabled
- && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
- && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
+ && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
+ && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
+ } else if (launchWalletOptionOnPowerDoubleTap()
+ && mWalletDoubleTapPowerEnabled
+ && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
+ && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
+ launchWallet = true;
+ intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
@@ -608,6 +716,10 @@ public class GestureLauncherService extends SystemService {
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
+ } else if (launchWallet) {
+ Slog.i(TAG, "Power button double tap gesture detected, launching wallet. Interval="
+ + powerTapInterval + "ms");
+ launchWallet = sendGestureTargetActivityPendingIntent();
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
@@ -623,11 +735,75 @@ public class GestureLauncherService extends SystemService {
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
- outLaunched.value = launchCamera || launchEmergencyGesture;
+ outLaunched.value = launchCamera || launchEmergencyGesture || launchWallet;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
+
+ /**
+ * Fetches and sends gestureTargetActivityPendingIntent from QuickAccessWallet, which is a
+ * specific activity that QuickAccessWalletService has defined to be launch on detection of the
+ * power button gesture.
+ */
+ private boolean sendGestureTargetActivityPendingIntent() {
+ boolean userSetupComplete = isUserSetupComplete();
+ if (mQuickAccessWalletClient == null
+ || !mQuickAccessWalletClient.isWalletServiceAvailable()) {
+ Slog.w(TAG, "QuickAccessWalletService is not available, ignoring wallet gesture.");
+ return false;
+ }
+
+ if (!userSetupComplete) {
+ if (DBG) {
+ Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
+ }
+ return false;
+ }
+ if (DBG) {
+ Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
+ }
+
+ mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
+ getContext().getMainExecutor(),
+ gesturePendingIntent -> {
+ if (gesturePendingIntent == null) {
+ Slog.d(TAG, "getGestureTargetActivityPendingIntent is null.");
+ sendFallbackPendingIntent();
+ return;
+ }
+ sendPendingIntentWithBackgroundStartPrivileges(gesturePendingIntent);
+ });
+ return true;
+ }
+
+ /**
+ * If gestureTargetActivityPendingIntent is null, this method is invoked to start the activity
+ * that QuickAccessWalletService has defined to host the Wallet view, which is typically the
+ * home screen of the Wallet application.
+ */
+ private void sendFallbackPendingIntent() {
+ mQuickAccessWalletClient.getWalletPendingIntent(
+ getContext().getMainExecutor(),
+ walletPendingIntent -> {
+ if (walletPendingIntent == null) {
+ Slog.w(TAG, "getWalletPendingIntent returns null. Not launching "
+ + "anything for wallet.");
+ return;
+ }
+ sendPendingIntentWithBackgroundStartPrivileges(walletPendingIntent);
+ });
+ }
+
+ private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendingIntent) {
+ try {
+ pendingIntent.send(GRANT_BACKGROUND_START_PRIVILEGES);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "PendingIntent was canceled", e);
+ }
+ }
+
+
/**
* @return true if camera was launched, false otherwise.
*/
@@ -709,6 +885,9 @@ public class GestureLauncherService extends SystemService {
registerContentObservers();
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ updateWalletDoubleTapPowerEnabled();
+ }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
}
@@ -720,6 +899,9 @@ public class GestureLauncherService extends SystemService {
if (userId == mUserId) {
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ updateWalletDoubleTapPowerEnabled();
+ }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
}
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 354f281551b2..aa06b7ecf76c 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -316,8 +316,7 @@ class BroadcastController {
return null;
}
if (callerApp.info.uid != SYSTEM_UID
- && !callerApp.getPkgList().containsKey(callerPackage)
- && !"android".equals(callerPackage)) {
+ && !callerApp.getPkgList().containsKey(callerPackage)) {
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f42641ece09b..aadf6f61956c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2840,7 +2840,6 @@ public class OomAdjuster {
return true;
}
}
- capability |= PROCESS_CAPABILITY_CPU_TIME;
}
// Not doing bind OOM management, so treat
// this guy more like a started service.
@@ -3089,7 +3088,6 @@ public class OomAdjuster {
return true;
}
}
- capability |= PROCESS_CAPABILITY_CPU_TIME;
}
}
if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
@@ -4243,6 +4241,11 @@ public class OomAdjuster {
!= client.getSetCapability()) {
// The connection might elevate the importance of the service's capabilities.
needDryRun = true;
+ } else if (Flags.useCpuTimeCapability()
+ && (client.getSetCapability() & ~app.getSetCapability()
+ & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ // The connection might grant PROCESS_CAPABILITY_CPU_TIME to the service.
+ needDryRun = true;
} else if (Flags.unfreezeBindPolicyFix()
&& cr.hasFlag(Context.BIND_WAIVE_PRIORITY
| Context.BIND_ALLOW_OOM_MANAGEMENT)) {
@@ -4290,6 +4293,10 @@ public class OomAdjuster {
&& client.mOptRecord.shouldNotFreeze()) {
// Process has shouldNotFreeze and it could have gotten it from the client.
return true;
+ } else if (Flags.useCpuTimeCapability()
+ && (client.getSetCapability() & app.getSetCapability()
+ & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ return true;
}
return false;
}
@@ -4309,6 +4316,11 @@ public class OomAdjuster {
&& client.mOptRecord.shouldNotFreeze()
&& !app.mOptRecord.shouldNotFreeze()) {
needDryRun = true;
+ } else if (Flags.useCpuTimeCapability()
+ && (client.getSetCapability() & ~app.getSetCapability()
+ & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ // The connection might grant PROCESS_CAPABILITY_CPU_TIME to the provider.
+ needDryRun = true;
}
if (needDryRun) {
@@ -4335,6 +4347,10 @@ public class OomAdjuster {
&& client.mOptRecord.shouldNotFreeze()) {
// Process has shouldNotFreeze and it could have gotten it from the client.
return true;
+ } else if (Flags.useCpuTimeCapability()
+ && (client.getSetCapability() & app.getSetCapability()
+ & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+ return true;
}
return false;
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 83b0801ce87f..50d650855b05 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -37,7 +37,6 @@ class BrightnessRangeController {
private final HdrClamper mHdrClamper;
private final Runnable mModeChangeCallback;
- private final boolean mUseNbmController;
private final boolean mUseHdrClamper;
@@ -62,11 +61,8 @@ class BrightnessRangeController {
mHdrClamper = hdrClamper;
mNormalBrightnessModeController = normalBrightnessModeController;
mUseHdrClamper = flags.isHdrClamperEnabled() && !flags.useNewHdrBrightnessModifier();
- mUseNbmController = flags.isNbmControllerEnabled();
- if (mUseNbmController) {
- mNormalBrightnessModeController.resetNbmData(
- displayDeviceConfig.getLuxThrottlingData());
- }
+ mNormalBrightnessModeController.resetNbmData(
+ displayDeviceConfig.getLuxThrottlingData());
if (flags.useNewHdrBrightnessModifier()) {
// HDR boost is handled by HdrBrightnessModifier and should be disabled in HbmController
mHbmController.disableHdrBoost();
@@ -76,7 +72,6 @@ class BrightnessRangeController {
void dump(PrintWriter pw) {
pw.println("BrightnessRangeController:");
- pw.println(" mUseNormalBrightnessController=" + mUseNbmController);
pw.println(" mUseHdrClamper=" + mUseHdrClamper);
mHbmController.dump(pw);
mNormalBrightnessModeController.dump(pw);
@@ -138,9 +133,7 @@ class BrightnessRangeController {
float getCurrentBrightnessMax() {
// nbmController might adjust maxBrightness only if device does not support HBM or
// hbm is currently not allowed
- if (mUseNbmController
- && (!mHbmController.deviceSupportsHbm()
- || !mHbmController.isHbmCurrentlyAllowed())) {
+ if (!mHbmController.deviceSupportsHbm() || !mHbmController.isHbmCurrentlyAllowed()) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
}
@@ -173,16 +166,12 @@ class BrightnessRangeController {
}
private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
- if (mUseNbmController) {
- boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
- hbmChangesFunc.run();
- // if nbm transition changed - trigger callback
- // HighBrightnessModeController handles sending changes itself
- if (nbmTransitionChanged) {
- mModeChangeCallback.run();
- }
- } else {
- hbmChangesFunc.run();
+ boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
+ hbmChangesFunc.run();
+ // if nbm transition changed - trigger callback
+ // HighBrightnessModeController handles sending changes itself
+ if (nbmTransitionChanged) {
+ mModeChangeCallback.run();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f48fbea64f65..9387e9ede532 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1646,6 +1646,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
(mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
|| mAutomaticBrightnessStrategy
.isTemporaryAutoBrightnessAdjustmentApplied();
+ float rampSpeed = 0;
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1747,7 +1748,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
customAnimationRate, /* ignoreAnimationLimits = */true);
} else {
boolean isIncreasing = animateValue > currentBrightness;
- final float rampSpeed;
final boolean idle = mAutomaticBrightnessController != null
&& mAutomaticBrightnessController.isInIdleMode();
if (isIncreasing && slowChange) {
@@ -1832,6 +1832,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
.getDisplayBrightnessStrategyName());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(
displayBrightnessState.getShouldUseAutoBrightness());
+ mTempBrightnessEvent.setSlowChange(slowChange);
+ mTempBrightnessEvent.setRampSpeed(rampSpeed);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 9e9b899ffa7d..159c30ddf77f 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -78,6 +78,8 @@ public final class BrightnessEvent {
private String mDisplayBrightnessStrategyName;
@AutomaticBrightnessController.AutomaticBrightnessMode
private int mAutoBrightnessMode;
+ private boolean mSlowChange;
+ private float mRampSpeed;
public BrightnessEvent(BrightnessEvent that) {
copyFrom(that);
@@ -126,6 +128,8 @@ public final class BrightnessEvent {
mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
mDisplayBrightnessStrategyName = that.getDisplayBrightnessStrategyName();
mAutoBrightnessMode = that.mAutoBrightnessMode;
+ mSlowChange = that.mSlowChange;
+ mRampSpeed = that.mRampSpeed;
}
/**
@@ -163,6 +167,8 @@ public final class BrightnessEvent {
mAutomaticBrightnessEnabled = true;
mDisplayBrightnessStrategyName = "";
mAutoBrightnessMode = AUTO_BRIGHTNESS_MODE_DEFAULT;
+ mSlowChange = false;
+ mRampSpeed = 0;
}
/**
@@ -248,7 +254,9 @@ public final class BrightnessEvent {
+ ", powerFactor=" + mPowerFactor
// Meta
+ ", physDisp=" + mPhysicalDisplayName + "(" + mPhysicalDisplayId + ")"
- + ", logicalId=" + mDisplayId;
+ + ", logicalId=" + mDisplayId
+ + ", slowChange=" + mSlowChange
+ + ", rampSpeed=" + mRampSpeed;
}
@Override
@@ -469,8 +477,8 @@ public final class BrightnessEvent {
return mDisplayBrightnessStrategyName;
}
- public void setAutomaticBrightnessEnabled(boolean mAutomaticBrightnessEnabled) {
- this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
+ public void setAutomaticBrightnessEnabled(boolean automaticBrightnessEnabled) {
+ mAutomaticBrightnessEnabled = automaticBrightnessEnabled;
}
@AutomaticBrightnessController.AutomaticBrightnessMode
@@ -483,6 +491,14 @@ public final class BrightnessEvent {
mAutoBrightnessMode = mode;
}
+ public void setSlowChange(boolean slowChange) {
+ mSlowChange = slowChange;
+ }
+
+ public void setRampSpeed(float rampSpeed) {
+ mRampSpeed = rampSpeed;
+ }
+
/**
* A utility to stringify flags from a BrightnessEvent
* @return Stringified flags from BrightnessEvent
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 7892639fc8ed..52e64905c984 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -168,8 +168,7 @@ public final class ColorDisplayService extends SystemService {
new NightDisplayTintController();
private final TintController mGlobalSaturationTintController =
new GlobalSaturationTintController();
- private final ReduceBrightColorsTintController mReduceBrightColorsTintController =
- new ReduceBrightColorsTintController();
+ private final ReduceBrightColorsTintController mReduceBrightColorsTintController;
@VisibleForTesting
final Handler mHandler;
@@ -201,7 +200,13 @@ public final class ColorDisplayService extends SystemService {
private boolean mEvenDimmerActivated;
public ColorDisplayService(Context context) {
+ this(context, new ReduceBrightColorsTintController());
+ }
+
+ @VisibleForTesting
+ public ColorDisplayService(Context context, ReduceBrightColorsTintController rbcController) {
super(context);
+ mReduceBrightColorsTintController = rbcController;
mHandler = new TintHandler(DisplayThread.get().getLooper());
mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled();
mUserManager = UserManagerService.getInstance();
@@ -571,27 +576,37 @@ public final class ColorDisplayService extends SystemService {
return mColorModeCompositionColorSpaces.get(mode, Display.COLOR_MODE_INVALID);
}
- private void onDisplayColorModeChanged(int mode) {
+ @VisibleForTesting
+ void onDisplayColorModeChanged(int mode) {
if (mode == NOT_SET) {
return;
}
+ mReduceBrightColorsTintController.cancelAnimator();
mNightDisplayTintController.cancelAnimator();
mDisplayWhiteBalanceTintController.cancelAnimator();
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+
if (mNightDisplayTintController.isAvailable(getContext())) {
- final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
mNightDisplayTintController.setUp(getContext(), dtm.needsLinearColorMatrix(mode));
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
}
+ if (mReduceBrightColorsTintController.isAvailable(getContext())) {
+ // Different color modes may require different coefficients to be loaded for RBC.
+ // Re-set up RBC so that it can recalculate its transform matrix with new values.
+ mReduceBrightColorsTintController.setUp(getContext(), dtm.needsLinearColorMatrix(mode));
+ onReduceBrightColorsStrengthLevelChanged(); // Trigger matrix recalc + updates
+ }
+
// dtm.setColorMode() needs to be called before
// updateDisplayWhiteBalanceStatus(), this is because the latter calls
// DisplayTransformManager.needsLinearColorMatrix(), therefore it is dependent
// on the state of DisplayTransformManager.
- final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
dtm.setColorMode(mode, mNightDisplayTintController.getMatrix(),
+ mReduceBrightColorsTintController.getMatrix(),
getCompositionColorSpace(mode));
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index a76c427bec0e..cb7b1773e47e 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -265,7 +265,7 @@ public class DisplayTransformManager {
/**
* Sets color mode and updates night display transform values.
*/
- public boolean setColorMode(int colorMode, float[] nightDisplayMatrix,
+ public boolean setColorMode(int colorMode, float[] nightDisplayMatrix, float[] rbcMatrix,
int compositionColorMode) {
if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
applySaturation(COLOR_SATURATION_NATURAL);
@@ -285,7 +285,11 @@ public class DisplayTransformManager {
setDisplayColor(colorMode, compositionColorMode);
}
+ // These are close to the setDisplayColor() call to reduce delay between
+ // setting these matrixes and updating the color mode. Without this proximity
+ // of calls, updates to color mode can result in flicker.
setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix);
+ setColorMatrix(LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS, rbcMatrix);
updateConfiguration();
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 78bd41bd2e11..85b6bbb40b91 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -46,10 +46,6 @@ public class DisplayManagerFlags {
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
Flags::enableConnectedDisplayManagement);
- private final FlagState mNbmControllerFlagState = new FlagState(
- Flags.FLAG_ENABLE_NBM_CONTROLLER,
- Flags::enableNbmController);
-
private final FlagState mHdrClamperFlagState = new FlagState(
Flags.FLAG_ENABLE_HDR_CLAMPER,
Flags::enableHdrClamper);
@@ -282,11 +278,6 @@ public class DisplayManagerFlags {
return mConnectedDisplayManagementFlagState.isEnabled();
}
- /** Returns whether NBM Controller is enabled or not. */
- public boolean isNbmControllerEnabled() {
- return mNbmControllerFlagState.isEnabled();
- }
-
/** Returns whether hdr clamper is enabled on not. */
public boolean isHdrClamperEnabled() {
return mHdrClamperFlagState.isEnabled();
@@ -595,7 +586,6 @@ public class DisplayManagerFlags {
pw.println(" " + mExternalDisplayLimitModeState);
pw.println(" " + mDisplayTopology);
pw.println(" " + mHdrClamperFlagState);
- pw.println(" " + mNbmControllerFlagState);
pw.println(" " + mPowerThrottlingClamperFlagState);
pw.println(" " + mEvenDimmerFlagState);
pw.println(" " + mSmallAreaDetectionFlagState);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 123b7dfbf843..3358f723709c 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -37,14 +37,6 @@ flag {
}
flag {
- name: "enable_nbm_controller"
- namespace: "display_manager"
- description: "Feature flag for Normal Brightness Mode Controller"
- bug: "299527549"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_hdr_clamper"
namespace: "display_manager"
description: "Feature flag for HDR Clamper"
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 7505c710f483..424102cbdd89 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -212,11 +212,11 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
HdmiConfig.TIMEOUT_MS);
}
- launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
- reason != HdmiControlService.INITIATED_BY_BOOT_UP);
resetSelectRequestBuffer();
launchDeviceDiscovery();
startQueuedActions();
+ final boolean routingForBootup = reason != HdmiControlService.INITIATED_BY_ENABLE_CEC
+ && reason != HdmiControlService.INITIATED_BY_BOOT_UP;
List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer
.getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE);
if (bufferedActiveSource.isEmpty()) {
@@ -227,14 +227,8 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
- if (!mService.getLocalActiveSource().isValid()
- && result != HdmiControlManager.RESULT_SUCCESS) {
- mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
- getDeviceInfo().getLogicalAddress(),
- getDeviceInfo().getPhysicalAddress()));
- updateActiveSource(getDeviceInfo().getLogicalAddress(),
- getDeviceInfo().getPhysicalAddress(),
- "RequestActiveSourceAction#finishWithCallback()");
+ if (result != HdmiControlManager.RESULT_SUCCESS) {
+ launchRoutingControl(routingForBootup);
}
}
}));
@@ -1384,8 +1378,7 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
} else {
int activePath = mService.getPhysicalAddress();
setActivePath(activePath);
- if (!routingForBootup
- && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+ if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildActiveSource(
getDeviceInfo().getLogicalAddress(), activePath));
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 78c10453cd95..4e1df769100b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -112,9 +112,10 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public int openSession(HubEndpointInfo destination, String serviceDescriptor)
throws RemoteException {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.openSession_enforcePermission();
if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
int sessionId = mEndpointManager.reserveSessionId();
EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
@@ -139,8 +140,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void closeSession(int sessionId, int reason) throws RemoteException {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.closeSession_enforcePermission();
if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
try {
mContextHubProxy.closeEndpointSession(sessionId, (byte) reason);
@@ -151,8 +153,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void unregister() {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.unregister_enforcePermission();
mIsRegistered.set(false);
try {
mContextHubProxy.unregisterEndpoint(mHalEndpointInfo);
@@ -174,8 +177,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void openSessionRequestComplete(int sessionId) {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.openSessionRequestComplete_enforcePermission();
synchronized (mOpenSessionLock) {
try {
mContextHubProxy.endpointSessionOpenComplete(sessionId);
@@ -187,9 +191,10 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void sendMessage(
int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.sendMessage_enforcePermission();
Message halMessage = ContextHubServiceUtil.createHalMessage(message);
synchronized (mOpenSessionLock) {
if (!mActiveSessionIds.contains(sessionId)
@@ -227,8 +232,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
@Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode) {
- ContextHubServiceUtil.checkPermissions(mContext);
+ super.sendMessageDeliveryStatus_enforcePermission();
MessageDeliveryStatus status = new MessageDeliveryStatus();
status.messageSequenceNumber = messageSeqNumber;
status.errorCode = errorCode;
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d8c35358102d..f09be2c15ee0 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -289,7 +289,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
// doesn't have any registered discovery preference, we should still be able to route their
// system media.
boolean bindDueToSystemMediaRoutingSupport =
- mIsManagerScanning && mSupportsSystemMediaRouting;
+ mLastDiscoveryPreference != null
+ && mLastDiscoveryPreference.shouldPerformActiveScan()
+ && mSupportsSystemMediaRouting;
if (!getSessionInfos().isEmpty()
|| bindDueToManagerScan
|| bindDueToSystemMediaRoutingSupport) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 69c460e0f19d..42303e042561 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -32,6 +32,7 @@ import android.content.pm.ServiceInfo;
import android.media.MediaRoute2ProviderService;
import android.os.Handler;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -162,8 +163,14 @@ final class MediaRoute2ProviderWatcher {
mUserId);
Slog.i(
TAG,
- "Enabling proxy for MediaRoute2ProviderService: "
- + proxy.mComponentName);
+ TextUtils.formatSimple(
+ "Enabling proxy for MediaRoute2ProviderService: %s"
+ + " (isSelfScan=%b, supportsSystemMediaRouting=%b,"
+ + " userId=%d)",
+ proxy.mComponentName,
+ isSelfScanOnlyProvider,
+ supportsSystemMediaRouting,
+ mUserId));
proxy.start(/* rebindIfDisconnected= */ false);
mProxies.add(targetIndex++, proxy);
mCallback.onAddProviderService(proxy);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e18ed410c045..58deffcbd4ba 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -848,7 +848,7 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
List<RoutingSessionInfo> sessionInfos;
if (hasSystemRoutingPermissions) {
- if (setDeviceRouteSelected) {
+ if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
return userRecord.mHandler.getSystemProvider()
@@ -2733,6 +2733,15 @@ class MediaRouter2ServiceImpl {
newRoutes = Collections.emptySet();
}
+ if (Flags.enableMirroringInMediaRouter2()
+ && provider instanceof MediaRoute2ProviderServiceProxy proxyProvider) {
+ // We notify the system provider of service updates, so that it can update the
+ // system routing session by adding them as transferable routes. And we remove those
+ // that don't support remote routing.
+ mSystemProvider.updateSystemMediaRoutesFromProxy(proxyProvider);
+ newRoutes.removeIf(it -> !it.supportsRemoteRouting());
+ }
+
// Add new routes to the maps.
ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
boolean hasAddedOrModifiedRoutes = false;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8dfba39dcfd8..b93846bf9ee7 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -73,6 +73,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
// For apps without MODIFYING_AUDIO_ROUTING permission.
// This should be the currently selected route.
MediaRoute2Info mDefaultRoute;
+
+ @GuardedBy("mLock")
+ RoutingSessionInfo mSystemSessionInfo;
+
RoutingSessionInfo mDefaultSessionInfo;
private final AudioManagerBroadcastReceiver mAudioReceiver =
@@ -180,7 +184,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
if (TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
RoutingSessionInfo currentSessionInfo;
synchronized (mLock) {
- currentSessionInfo = mSessionInfos.get(0);
+ currentSessionInfo =
+ Flags.enableMirroringInMediaRouter2()
+ ? mSystemSessionInfo
+ : mSessionInfos.get(0);
}
mCallback.onSessionCreated(this, requestId, currentSessionInfo);
return;
@@ -354,7 +361,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
- RoutingSessionInfo oldSessionInfo = mSessionInfos.get(0);
+ var oldSessionInfo =
+ Flags.enableMirroringInMediaRouter2()
+ ? mSystemSessionInfo
+ : mSessionInfos.get(0);
builder.setTransferReason(oldSessionInfo.getTransferReason())
.setTransferInitiator(oldSessionInfo.getTransferInitiatorUserHandle(),
oldSessionInfo.getTransferInitiatorPackageName());
@@ -364,6 +374,31 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
}
+ /**
+ * Notifies the system provider of a {@link MediaRoute2ProviderServiceProxy} update.
+ *
+ * <p>To be overridden so as to generate system media routes for {@link
+ * MediaRoute2ProviderService} routes that {@link MediaRoute2Info#supportsSystemMediaRouting()
+ * support system media routing}).
+ *
+ * @param serviceProxy The proxy of the service that updated its state.
+ */
+ public void updateSystemMediaRoutesFromProxy(MediaRoute2ProviderServiceProxy serviceProxy) {
+ // Do nothing. This implementation doesn't support MR2ProviderService system media routes.
+ // The subclass overrides this method to implement app-managed system media routing (aka
+ // mirroring).
+ }
+
+ /**
+ * Called when the system provider state changes.
+ *
+ * <p>To be overridden by {@link SystemMediaRoute2Provider2}, so that app-provided system media
+ * routing routes are added before setting the provider state.
+ */
+ public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) {
+ setProviderState(providerInfo);
+ }
+
protected void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
@@ -373,7 +408,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
for (MediaRoute2Info route : deviceRoutes) {
builder.addRoute(route);
}
- setProviderState(builder.build());
+ if (!Flags.enableMirroringInMediaRouter2()) {
+ setProviderState(builder.build());
+ }
} else {
builder.addRoute(mDeviceRouteController.getSelectedRoute());
}
@@ -382,7 +419,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
builder.addRoute(route);
}
MediaRoute2ProviderInfo providerInfo = builder.build();
- setProviderState(providerInfo);
+ onSystemProviderRoutesChanged(providerInfo);
if (DEBUG) {
Slog.d(TAG, "Updating system provider info : " + providerInfo);
}
@@ -393,8 +430,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
*/
boolean updateSessionInfosIfNeeded() {
synchronized (mLock) {
- RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
- 0);
+ RoutingSessionInfo oldSessionInfo;
+ if (Flags.enableMirroringInMediaRouter2()) {
+ oldSessionInfo = mSystemSessionInfo;
+ } else {
+ oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(0);
+ }
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, "" /* clientPackageName */)
@@ -483,8 +524,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
if (DEBUG) {
Slog.d(TAG, "Updating system routing session info : " + newSessionInfo);
}
- mSessionInfos.clear();
- mSessionInfos.add(newSessionInfo);
+ mSystemSessionInfo = newSessionInfo;
+ onSystemSessionInfoUpdated();
mDefaultSessionInfo =
new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, "" /* clientPackageName */)
@@ -501,6 +542,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
}
+ @GuardedBy("mLock")
+ protected void onSystemSessionInfoUpdated() {
+ mSessionInfos.clear();
+ mSessionInfos.add(mSystemSessionInfo);
+ }
+
@GuardedBy("mRequestLock")
private void reportPendingSessionRequestResultLockedIfNeeded(
RoutingSessionInfo newSessionInfo) {
@@ -587,6 +634,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
RoutingSessionInfo sessionInfo;
synchronized (mLock) {
sessionInfo = mSessionInfos.get(0);
+ if (sessionInfo == null) {
+ return;
+ }
}
mCallback.onSessionUpdated(this, sessionInfo);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 85b30ad8cadb..7dc30ab66fd2 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -16,11 +16,26 @@
package com.android.server.media;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.RoutingSessionInfo;
import android.os.Looper;
import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
/**
* Extends {@link SystemMediaRoute2Provider} by adding system routes provided by {@link
@@ -30,6 +45,15 @@ import android.os.UserHandle;
*/
/* package */ class SystemMediaRoute2Provider2 extends SystemMediaRoute2Provider {
+ private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM";
+ private static final String ROUTE_ID_SYSTEM_SEPARATOR = ".";
+
+ @GuardedBy("mLock")
+ private MediaRoute2ProviderInfo mLastSystemProviderInfo;
+
+ @GuardedBy("mLock")
+ private final Map<String, ProviderProxyRecord> mProxyRecords = new HashMap<>();
+
private static final ComponentName COMPONENT_NAME =
new ComponentName(
SystemMediaRoute2Provider2.class.getPackage().getName(),
@@ -46,4 +70,117 @@ import android.os.UserHandle;
private SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
super(context, COMPONENT_NAME, user, looper);
}
+
+ @Override
+ protected void onSystemSessionInfoUpdated() {
+ updateSessionInfo();
+ }
+
+ @Override
+ public void updateSystemMediaRoutesFromProxy(MediaRoute2ProviderServiceProxy serviceProxy) {
+ var proxyRecord = ProviderProxyRecord.createFor(serviceProxy);
+ synchronized (mLock) {
+ if (proxyRecord == null) {
+ mProxyRecords.remove(serviceProxy.mUniqueId);
+ } else {
+ mProxyRecords.put(serviceProxy.mUniqueId, proxyRecord);
+ }
+ setProviderState(buildProviderInfo());
+ }
+ updateSessionInfo();
+ notifyProviderState();
+ notifySessionInfoUpdated();
+ }
+
+ @Override
+ public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) {
+ synchronized (mLock) {
+ mLastSystemProviderInfo = providerInfo;
+ setProviderState(buildProviderInfo());
+ }
+ updateSessionInfo();
+ notifySessionInfoUpdated();
+ }
+
+ /**
+ * Updates the {@link #mSessionInfos} by expanding the {@link SystemMediaRoute2Provider} session
+ * with information from the {@link MediaRoute2ProviderService provider services}.
+ */
+ private void updateSessionInfo() {
+ synchronized (mLock) {
+ var systemSessionInfo = mSystemSessionInfo;
+ if (systemSessionInfo == null) {
+ // The system session info hasn't been initialized yet. Do nothing.
+ return;
+ }
+ var builder = new RoutingSessionInfo.Builder(systemSessionInfo);
+ mProxyRecords.values().stream()
+ .flatMap(ProviderProxyRecord::getRoutesStream)
+ .map(MediaRoute2Info::getId)
+ .forEach(builder::addTransferableRoute);
+ mSessionInfos.clear();
+ mSessionInfos.add(builder.build());
+ }
+ }
+
+ /**
+ * Returns a new a provider info that includes all routes from the system provider {@link
+ * SystemMediaRoute2Provider}, along with system routes from {@link MediaRoute2ProviderService
+ * provider services}.
+ */
+ @GuardedBy("mLock")
+ private MediaRoute2ProviderInfo buildProviderInfo() {
+ MediaRoute2ProviderInfo.Builder builder =
+ new MediaRoute2ProviderInfo.Builder(mLastSystemProviderInfo);
+ mProxyRecords.values().stream()
+ .flatMap(ProviderProxyRecord::getRoutesStream)
+ .forEach(builder::addRoute);
+ return builder.build();
+ }
+
+ /**
+ * Holds information about {@link MediaRoute2ProviderService provider services} registered in
+ * the system.
+ *
+ * @param mProxy The corresponding {@link MediaRoute2ProviderServiceProxy}.
+ * @param mSystemMediaRoutes The last snapshot of routes from the service that support system
+ * media routing, as defined by {@link MediaRoute2Info#supportsSystemMediaRouting()}.
+ */
+ private record ProviderProxyRecord(
+ MediaRoute2ProviderServiceProxy mProxy,
+ Collection<MediaRoute2Info> mSystemMediaRoutes) {
+
+ /** Returns a stream representation of the {@link #mSystemMediaRoutes}. */
+ public Stream<MediaRoute2Info> getRoutesStream() {
+ return mSystemMediaRoutes.stream();
+ }
+
+ /**
+ * Returns a new instance, or null if the given {@code serviceProxy} doesn't have an
+ * associated {@link MediaRoute2ProviderInfo}.
+ */
+ @Nullable
+ public static ProviderProxyRecord createFor(MediaRoute2ProviderServiceProxy serviceProxy) {
+ MediaRoute2ProviderInfo providerInfo = serviceProxy.getProviderInfo();
+ if (providerInfo == null) {
+ return null;
+ }
+ ArraySet<MediaRoute2Info> routes = new ArraySet<>();
+ providerInfo.getRoutes().stream()
+ .filter(MediaRoute2Info::supportsSystemMediaRouting)
+ .forEach(
+ route -> {
+ String id =
+ ROUTE_ID_PREFIX_SYSTEM
+ + route.getProviderId()
+ + ROUTE_ID_SYSTEM_SEPARATOR
+ + route.getOriginalId();
+ routes.add(
+ new MediaRoute2Info.Builder(id, route.getName())
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .build());
+ });
+ return new ProviderProxyRecord(serviceProxy, Collections.unmodifiableSet(routes));
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 3e488bf2207f..c81023144c0f 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -42,6 +42,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -138,7 +139,7 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- getAllMediaProfileColumns(), selection, selectionArguments)
+ getMediaProfileColumns(includeParams), selection, selectionArguments)
) {
int count = cursor.getCount();
if (count == 0) {
@@ -160,8 +161,8 @@ public class MediaQualityService extends SystemService {
String packageName, boolean includeParams, UserHandle user) {
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
- return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
- selectionArguments);
+ return getPictureProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+ selection, selectionArguments);
}
@Override
@@ -259,7 +260,7 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- getAllMediaProfileColumns(), selection, selectionArguments)
+ getMediaProfileColumns(includeParams), selection, selectionArguments)
) {
int count = cursor.getCount();
if (count == 0) {
@@ -281,8 +282,8 @@ public class MediaQualityService extends SystemService {
String packageName, boolean includeParams, UserHandle user) {
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
- return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
- selectionArguments);
+ return getSoundProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+ selection, selectionArguments);
}
@Override
@@ -406,15 +407,18 @@ public class MediaQualityService extends SystemService {
return values;
}
- private String[] getAllMediaProfileColumns() {
- return new String[]{
+ private String[] getMediaProfileColumns(boolean includeParams) {
+ ArrayList<String> columns = new ArrayList<>(Arrays.asList(
BaseParameters.PARAMETER_ID,
BaseParameters.PARAMETER_TYPE,
BaseParameters.PARAMETER_NAME,
BaseParameters.PARAMETER_INPUT_ID,
- BaseParameters.PARAMETER_PACKAGE,
- mMediaQualityDbHelper.SETTINGS
- };
+ BaseParameters.PARAMETER_PACKAGE)
+ );
+ if (includeParams) {
+ columns.add(mMediaQualityDbHelper.SETTINGS);
+ }
+ return columns.toArray(new String[0]);
}
private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
diff --git a/services/core/java/com/android/server/pm/RestrictionsSet.java b/services/core/java/com/android/server/pm/RestrictionsSet.java
index 08047695a42a..38075c135d2a 100644
--- a/services/core/java/com/android/server/pm/RestrictionsSet.java
+++ b/services/core/java/com/android/server/pm/RestrictionsSet.java
@@ -65,6 +65,7 @@ public class RestrictionsSet {
throw new IllegalArgumentException("empty restriction bundle cannot be added.");
}
mUserRestrictions.put(userId, restrictions);
+ UserManager.invalidateUserRestriction();
}
/**
@@ -84,6 +85,7 @@ public class RestrictionsSet {
} else {
mUserRestrictions.delete(userId);
}
+ UserManager.invalidateUserRestriction();
return true;
}
@@ -102,6 +104,9 @@ public class RestrictionsSet {
removed = true;
}
}
+ if (removed) {
+ UserManager.invalidateUserRestriction();
+ }
return removed;
}
@@ -129,6 +134,7 @@ public class RestrictionsSet {
i--;
}
}
+ UserManager.invalidateUserRestriction();
}
}
@@ -192,6 +198,7 @@ public class RestrictionsSet {
public boolean remove(@UserIdInt int userId) {
boolean hasUserRestriction = mUserRestrictions.contains(userId);
mUserRestrictions.remove(userId);
+ UserManager.invalidateUserRestriction();
return hasUserRestriction;
}
@@ -200,6 +207,7 @@ public class RestrictionsSet {
*/
public void removeAllRestrictions() {
mUserRestrictions.clear();
+ UserManager.invalidateUserRestriction();
}
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 066fce068d61..8249d65868cd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1113,6 +1113,7 @@ public class UserManagerService extends IUserManager.Stub {
UserManager.invalidateUserPropertiesCache();
}
UserManager.invalidateCacheOnUserListChange();
+ UserManager.invalidateUserRestriction();
}
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 09feb18d07bf..6ab30595e46b 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -216,12 +216,12 @@ final class DefaultPermissionGrantPolicy {
private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
static {
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+
if (Flags.replaceBodySensorPermissionEnabled()) {
SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEART_RATE);
SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
- } else {
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 05bc69a9f1f0..672eb4caf798 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -264,6 +264,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
persistentDeviceId, mPermissionManagerServiceImpl::checkUidPermission);
}
+ @Override
+ @Context.PermissionRequestState
+ public int getPermissionRequestState(@NonNull String packageName,
+ @NonNull String permissionName, int deviceId) {
+ Objects.requireNonNull(permissionName, "permission can't be null.");
+ Objects.requireNonNull(packageName, "package name can't be null.");
+ return mPermissionManagerServiceImpl.getPermissionRequestState(packageName, permissionName,
+ getPersistentDeviceId(deviceId));
+ }
+
private String getPersistentDeviceId(int deviceId) {
if (deviceId == Context.DEVICE_ID_DEFAULT) {
return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ea71953d7cb3..ca70bddc5ac1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1014,6 +1014,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
@Override
+ public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ throw new IllegalStateException("getPermissionRequestState is not supported.");
+ }
+
+ @Override
public Map<String, PermissionManager.PermissionState> getAllPermissionStates(
@NonNull String packageName, @NonNull String deviceId, int userId) {
throw new UnsupportedOperationException(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 754b141ff10d..b607832767a1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -20,6 +20,7 @@ import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -407,6 +408,16 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
int checkUidPermission(int uid, String permName, String deviceId);
/**
+ * Returns one of the permission state
+ * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_GRANTED},
+ * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_REQUESTABLE}, or
+ * {@link Context.PermissionRequestState#PERMISSION_REQUEST_STATE_UNREQUESTABLE}
+ * for permission request permission flow.
+ */
+ int getPermissionRequestState(@NonNull String packageName, @NonNull String permName,
+ @NonNull String deviceId);
+
+ /**
* Gets the permission states for requested package, persistent device and user.
* <p>
* <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index c18f856594ed..ba5e97e7b113 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -247,6 +247,13 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag
}
@Override
+ public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ Log.i(LOG_TAG, "checkUidPermissionState(permName = " + permName + ", deviceId = "
+ + deviceId + ", packageName = " + packageName + ")");
+ return mService.getPermissionRequestState(packageName, permName, deviceId);
+ }
+
+ @Override
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String deviceId, int userId) {
Log.i(LOG_TAG,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index 40139baf0e98..008c14db8b65 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -319,6 +319,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer
}
@Override
+ public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ return mNewImplementation.getPermissionRequestState(packageName, permName, deviceId);
+ }
+
+ @Override
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String deviceId, int userId) {
return mNewImplementation.getAllPermissionStates(packageName, deviceId, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index 981d3d92b15a..2a47f51da951 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -345,6 +345,18 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag
}
}
+
+ @Override
+ public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ Trace.traceBegin(TRACE_TAG,
+ "TaggedTracingPermissionManagerServiceImpl#checkUidPermissionState");
+ try {
+ return mService.getPermissionRequestState(packageName, permName, deviceId);
+ } finally {
+ Trace.traceEnd(TRACE_TAG);
+ }
+ }
+
@Override
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String deviceId, int userId) {
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 51e0195f13fc..aae7417970eb 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -341,6 +341,8 @@ public final class HintManagerService extends SystemService {
supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
supportInfo.headroom.isCpuSupported = false;
supportInfo.headroom.isGpuSupported = false;
+
+ supportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
if (isHintSessionSupported()) {
if (mPowerHalVersion == 4) {
// Assume we support the V4 hints & modes unless specified
@@ -1649,6 +1651,7 @@ public final class HintManagerService extends SystemService {
mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
}
+ @Override
public IHintManager.HintManagerClientData
registerClient(@NonNull IHintManager.IHintManagerClient clientBinder) {
IHintManager.HintManagerClientData out = new IHintManager.HintManagerClientData();
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
index f51c25d6761c..acdea881e3d1 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -48,7 +48,7 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
- setPolicy(enabled);
+ onAdvancedProtectionChanged(enabled);
}
@NonNull
@@ -94,7 +94,8 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
return false;
}
- private void setPolicy(boolean enabled) {
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
if (enabled) {
Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
mDevicePolicyManager.addUserRestrictionGlobally(
@@ -105,21 +106,4 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
}
}
-
- @Override
- public void onAdvancedProtectionChanged(boolean enabled) {
- setPolicy(enabled);
-
- // Leave 2G disabled even if APM is disabled.
- if (!enabled) {
- for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
- long oldAllowedTypes =
- telephonyManager.getAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
- long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
- telephonyManager.setAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 417d6a5d12ee..89c7a3d89a54 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -57,6 +57,7 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
@@ -103,6 +104,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -389,6 +391,14 @@ class ActivityMetricsLogger {
return;
}
if (mLastLaunchedActivity != null) {
+ if (mLastLaunchedActivity.mLaunchCookie != null) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "Transferring launch cookie=%s from=%s(%d) to=%s(%d)",
+ mLastLaunchedActivity.mLaunchCookie,
+ mLastLaunchedActivity.packageName,
+ System.identityHashCode(mLastLaunchedActivity), r.packageName,
+ System.identityHashCode(r));
+ }
// Transfer the launch cookie and launch root task because it is a consecutive
// launch event.
r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 29a71328127b..348d326b83d2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -137,6 +137,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATIO
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
@@ -2137,6 +2138,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
mLaunchCookie = options.getLaunchCookie();
mLaunchRootTask = options.getLaunchRootTask();
+ if (mLaunchCookie != null) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "Activity created with launch cookie=%s act=%s(%d)",
+ mLaunchCookie, packageName, System.identityHashCode(this));
+ }
} else {
mHasSceneTransition = false;
}
@@ -4141,6 +4147,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
r -> r.mLaunchCookie == null && !r.finishing && r.isUid(getUid()),
this, false /* includeBoundary */, false /* traverseTopToBottom */);
if (nextCookieTarget != null) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "Transferring launch cookie=%s on finish from=%s(%d) to=%s(%d)",
+ mLaunchCookie, packageName, System.identityHashCode(this),
+ nextCookieTarget.packageName, System.identityHashCode(nextCookieTarget));
nextCookieTarget.mLaunchCookie = mLaunchCookie;
mLaunchCookie = null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2781592c6b4f..acb93844c945 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -65,6 +65,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
@@ -3097,6 +3098,10 @@ class ActivityStarter {
// options if set.
if (mStartActivity.mLaunchCookie != null) {
intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "Updating launch cookie=%s act=%s(%d)",
+ intentActivity.mLaunchCookie, intentActivity.packageName,
+ System.identityHashCode(intentActivity));
}
if (mStartActivity.mPendingRemoteAnimation != null) {
intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a077a0b9a2ca..0aff1de72cb1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -54,6 +54,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
@@ -1268,7 +1269,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Checks if the caller can be shown in the given public display.
int userId = UserHandle.getUserId(callingUid);
int displayId = display.getDisplayId();
- boolean allowed = mWindowManager.mUmInternal.isUserVisible(userId, displayId);
+ boolean allowed = userId == UserHandle.USER_SYSTEM
+ || mWindowManager.mUmInternal.isUserVisible(userId, displayId);
ProtoLog.d(WM_DEBUG_TASKS,
"Launch on display check: %s launch for userId=%d on displayId=%d",
(allowed ? "allow" : "disallow"), userId, displayId);
@@ -2842,6 +2844,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS,
+ "Updating launch cookie=%s for start from recents act=%s(%d)",
+ targetActivity.mLaunchCookie, targetActivity.packageName,
+ System.identityHashCode(targetActivity));
}
} finally {
mActivityMetricsLogger.notifyActivityLaunched(launchingState,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e9e3c9ee389e..1a7c6b70f007 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -275,12 +275,8 @@ class BackNavigationController {
final boolean isOccluded = isKeyguardOccluded(window);
if (!canAnimate) {
backType = BackNavigationInfo.TYPE_CALLBACK;
- } else if ((window.getParent().getChildCount() > 1
- && window.getParent().getChildAt(0) != window)) {
- // TODO Dialog window does not need to attach on activity, check
- // window.mAttrs.type != TYPE_BASE_APPLICATION
- // Are we the top window of our parent? If not, we are a window on top of the
- // activity, we won't close the activity.
+ } else if (window.mAttrs.type != TYPE_BASE_APPLICATION) {
+ // The focus window belongs to an activity and it's not the base window.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 24a6f118ad04..4bcba13448e9 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -258,16 +258,13 @@ class InsetsPolicy {
* We also need to exclude certain types of insets source for client within specific windowing
* modes.
*
- * @param attrs the LayoutParams of the target
- * @param windowingMode the windowing mode of the target
- * @param isAlwaysOnTop is the target always on top
+ * @param target the target on which the policy is applied
* @param state the input inset state containing all the sources
* @return The state stripped of the necessary information.
*/
- InsetsState enforceInsetsPolicyForTarget(WindowManager.LayoutParams attrs,
- @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
- InsetsState state) {
+ InsetsState enforceInsetsPolicyForTarget(WindowState target, InsetsState state) {
final InsetsState originalState = state;
+ final WindowManager.LayoutParams attrs = target.getAttrs();
// The caller should not receive the visible insets provided by itself.
if (attrs.type == TYPE_INPUT_METHOD) {
@@ -316,12 +313,17 @@ class InsetsPolicy {
}
}
+ final @WindowConfiguration.WindowingMode int windowingMode = target.getWindowingMode();
if (WindowConfiguration.isFloating(windowingMode)
- || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
+ || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && target.isAlwaysOnTop())) {
// Keep frames, caption, and IME.
int types = WindowInsets.Type.captionBar();
if (windowingMode != WINDOWING_MODE_PINNED) {
- types |= WindowInsets.Type.ime();
+ if (!Flags.refactorInsetsController() || (mDisplayContent != null
+ && target == mDisplayContent.getImeInputTarget()
+ && (WindowInsets.Type.ime() & target.getRequestedVisibleTypes()) != 0)) {
+ types |= WindowInsets.Type.ime();
+ }
}
final InsetsState newState = new InsetsState();
newState.set(state, types);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bcd12f253299..b4a22b0dd034 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -4150,7 +4150,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
.setSourceCrop(cropBounds)
.setCaptureSecureLayers(true)
.setAllowProtected(true)
- .setHintForSeamlessTransition(isDisplayRotation)
+ // We always reroute this screenshot to the display, so this transition
+ // is ALWAYS seamless
+ .setHintForSeamlessTransition(true)
.build();
ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureLayers(captureArgs);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 90bf053dfbef..68b4b6f0ae91 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1631,8 +1631,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
final InsetsState rawInsetsState =
mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
- final InsetsState insetsStateForWindow = insetsPolicy.enforceInsetsPolicyForTarget(
- mAttrs, getWindowingMode(), isAlwaysOnTop(), rawInsetsState);
+ final InsetsState insetsStateForWindow = insetsPolicy.enforceInsetsPolicyForTarget(this,
+ rawInsetsState);
return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
includeTransient);
}
@@ -3303,7 +3303,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// just kill it. And if it is a window of foreground activity, the activity can be
// restarted automatically if needed.
Slog.w(TAG, "Exception thrown during dispatchAppVisibility " + this, e);
- if (android.os.Process.getUidForPid(mSession.mPid) == mSession.mUid) {
+ if (android.os.Process.getUidForPid(mSession.mPid) == mSession.mUid
+ && android.os.Process.getThreadGroupLeader(mSession.mPid) == mSession.mPid) {
android.os.Process.killProcess(mSession.mPid);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 82699ea3badb..01639cc3b516 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -61,6 +61,7 @@ cc_library_static {
"com_android_server_SystemServer.cpp",
"com_android_server_tv_TvUinputBridge.cpp",
"com_android_server_tv_TvInputHal.cpp",
+ "com_android_server_UsbAlsaDevice.cpp",
"com_android_server_UsbAlsaJackDetector.cpp",
"com_android_server_UsbAlsaMidiDevice.cpp",
"com_android_server_UsbDeviceManager.cpp",
diff --git a/services/core/jni/com_android_server_UsbAlsaDevice.cpp b/services/core/jni/com_android_server_UsbAlsaDevice.cpp
new file mode 100644
index 000000000000..166932f167ed
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbAlsaDevice.cpp
@@ -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.
+ */
+
+#define LOG_TAG "UsbAlsaDeviceJNI"
+
+#include <nativehelper/JNIPlatformHelp.h>
+#include <tinyalsa/asoundlib.h>
+
+#include <string>
+#include <vector>
+
+#include "jni.h"
+#include "utils/Log.h"
+
+static const std::vector<std::string> POSSIBLE_HARDWARE_VOLUME_MIXER_NAMES =
+ {"Headphone Playback Volume", "Headset Playback Volume", "PCM Playback Volume"};
+
+namespace android {
+
+static void android_server_UsbAlsaDevice_setVolume(JNIEnv* /*env*/, jobject /*thiz*/, jint card,
+ float volume) {
+ ALOGD("%s(%d, %f)", __func__, card, volume);
+ struct mixer* alsaMixer = mixer_open(card);
+ if (alsaMixer == nullptr) {
+ ALOGW("%s(%d, %f) returned as no mixer is opened", __func__, card, volume);
+ return;
+ }
+ struct mixer_ctl* ctl = nullptr;
+ for (const auto& mixerName : POSSIBLE_HARDWARE_VOLUME_MIXER_NAMES) {
+ ctl = mixer_get_ctl_by_name(alsaMixer, mixerName.c_str());
+ if (ctl != nullptr) {
+ break;
+ }
+ }
+ if (ctl == nullptr) {
+ ALOGW("%s(%d, %f) returned as no volume mixer is found", __func__, card, volume);
+ return;
+ }
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int error = mixer_ctl_set_percent(ctl, id, 100 * volume); error != 0) {
+ ALOGE("%s(%d, %f) failed, error=%d", __func__, card, volume, error);
+ return;
+ }
+ }
+ ALOGD("%s(%d, %f) succeed", __func__, card, volume);
+}
+
+static JNINativeMethod method_table[] = {
+ {"nativeSetVolume", "(IF)V", (void*)android_server_UsbAlsaDevice_setVolume},
+};
+
+int register_android_server_UsbAlsaDevice(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaDevice", method_table,
+ NELEM(method_table));
+}
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 09fd8d4ac02e..e3bd69c30de7 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,6 +33,7 @@ int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
int register_android_server_HintManagerService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
+int register_android_server_UsbAlsaDevice(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
int register_android_server_UsbDeviceManager(JavaVM* vm, JNIEnv* env);
@@ -98,6 +99,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_UsbDeviceManager(vm, env);
+ register_android_server_UsbAlsaDevice(env);
register_android_server_UsbAlsaJackDetector(env);
register_android_server_UsbAlsaMidiDevice(env);
register_android_server_UsbHostManager(env);
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0b7438cd1b17..018cf20e914f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -464,6 +464,48 @@ class PermissionService(private val service: AccessCheckingService) :
return size
}
+ override fun getPermissionRequestState(
+ packageName: String,
+ permissionName: String,
+ deviceId: String
+ ): Int {
+ val uid = Binder.getCallingUid()
+ val result = context.checkPermission(permissionName, Binder.getCallingPid(), uid)
+ if (result == PackageManager.PERMISSION_GRANTED) {
+ return Context.PERMISSION_REQUEST_STATE_GRANTED
+ }
+
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ val packageState =
+ packageManagerLocal.withFilteredSnapshot(uid, userId).use {
+ it.getPackageState(packageName)
+ } ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ val androidPackage = packageState.androidPackage
+ ?: return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ if (appId != packageState.appId) {
+ return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ }
+ val permission = service.getState {
+ with(policy) { getPermissions()[permissionName] }
+ }
+ if (permission == null || !permission.isRuntime) {
+ return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ }
+ if (permissionName !in androidPackage.requestedPermissions) {
+ return Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ }
+
+ val permissionFlags = service.getState {
+ getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+ }
+ return if (permissionFlags.hasAnyBit(UNREQUESTABLE_MASK)) {
+ Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE
+ } else {
+ Context.PERMISSION_REQUEST_STATE_REQUESTABLE
+ }
+ }
+
override fun checkUidPermission(uid: Int, permissionName: String, deviceId: String): Int {
val userId = UserHandle.getUserId(uid)
if (!userManagerInternal.exists(userId)) {
@@ -472,7 +514,7 @@ class PermissionService(private val service: AccessCheckingService) :
// PackageManagerInternal.getPackage(int) already checks package visibility and enforces
// that instant apps can't see shared UIDs. Note that on the contrary,
- // Note that PackageManagerInternal.getPackage(String) doesn't perform any checks.
+ // PackageManagerInternal.getPackage(String) doesn't perform any checks.
val androidPackage = packageManagerInternal.getPackage(uid)
if (androidPackage != null) {
// Note that PackageManagerInternal.getPackageStateInternal() is not filtered.
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 0ccaa6043f5f..073ee31ddd60 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -16,6 +16,11 @@
package com.android.server.supervision;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.internal.util.Preconditions.checkCallAuthorization;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -31,6 +36,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.os.Binder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -78,6 +84,9 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
+ if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
+ enforcePermission(INTERACT_ACROSS_USERS);
+ }
synchronized (getLockObject()) {
return getUserDataLocked(userId).supervisionEnabled;
}
@@ -151,7 +160,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
/** Returns whether the supervision app has profile owner status. */
private boolean isProfileOwner(@UserIdInt int userId) {
- ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
+ ComponentName profileOwner =
+ mDpmInternal != null ? mDpmInternal.getProfileOwnerAsUser(userId) : null;
return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
}
@@ -161,6 +171,12 @@ public class SupervisionService extends ISupervisionManager.Stub {
mContext.getResources().getString(R.string.config_systemSupervision));
}
+ /** Enforces that the caller has the given permission. */
+ private void enforcePermission(String permission) {
+ checkCallAuthorization(
+ mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED);
+ }
+
public static class Lifecycle extends SystemService {
private final SupervisionService mSupervisionService;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
index f589a2c9385c..7db6ea0bf86d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -60,12 +60,6 @@ class BrightnessRangeControllerTest {
}
@Test
- fun testMaxBrightness_HbmDisabledAndNotAllowed() {
- val controller = createController(nbmEnabled = false, hbmAllowed = false)
- assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
- }
-
- @Test
fun testMaxBrightness_transitionPointLessThanCurrentNbmLimit() {
val controller = createController(
hbmAllowed = false,
@@ -76,13 +70,11 @@ class BrightnessRangeControllerTest {
}
private fun createController(
- nbmEnabled: Boolean = true,
hbmSupported: Boolean = true,
hbmAllowed: Boolean = true,
hbmMaxBrightness: Float = MAX_BRIGHTNESS,
nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
): BrightnessRangeController {
- whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
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 8ca39194de3b..a4dfecb8ed96 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2626,8 +2626,8 @@ public final class DisplayPowerControllerTest {
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
- final NormalBrightnessModeController normalBrightnessModeController = mock(
- NormalBrightnessModeController.class);
+ final NormalBrightnessModeController normalBrightnessModeController =
+ new NormalBrightnessModeController();
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index df09b046ddd2..6d1e56d1f479 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -68,6 +68,8 @@ public final class BrightnessEventTest {
mBrightnessEvent.setAutomaticBrightnessEnabled(true);
mBrightnessEvent.setDisplayBrightnessStrategyName(DISPLAY_BRIGHTNESS_STRATEGY_NAME);
mBrightnessEvent.setAutoBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
+ mBrightnessEvent.setSlowChange(true);
+ mBrightnessEvent.setRampSpeed(0.3f);
}
@Test
@@ -88,7 +90,7 @@ public final class BrightnessEventTest {
+ "preLux=150.0, wasShortTermModelActive=true, autoBrightness=true (idle), "
+ "unclampedBrt=0.65, hbmMax=0.62, hbmMode=off, thrmMax=0.65, "
+ "rbcStrength=-1, powerFactor=0.2, physDisp=display_name(987654321), "
- + "logicalId=1";
+ + "logicalId=1, slowChange=true, rampSpeed=0.3";
assertEquals(expectedString, actualString);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index f391e409a717..4e81b3530b62 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -20,10 +20,13 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -89,6 +92,7 @@ public class ColorDisplayServiceTest {
private ColorDisplayService.BinderService mBinderService;
private Resources mResourcesSpy;
+ private ReduceBrightColorsTintController mRbcSpy;
private static final int[] MINIMAL_COLOR_MODES = new int[] {
ColorDisplayManager.COLOR_MODE_NATURAL,
@@ -135,7 +139,8 @@ public class ColorDisplayServiceTest {
mLocalServiceKeeperRule.overrideLocalService(
DisplayManagerInternal.class, mDisplayManagerInternal);
- mCds = new ColorDisplayService(mContext);
+ mRbcSpy = Mockito.spy(new ReduceBrightColorsTintController());
+ mCds = new ColorDisplayService(mContext, mRbcSpy);
mBinderService = mCds.new BinderService();
mLocalServiceKeeperRule.overrideLocalService(
ColorDisplayService.ColorDisplayServiceInternal.class,
@@ -1106,7 +1111,8 @@ public class ColorDisplayServiceTest {
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
- eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), any(),
+ eq(Display.COLOR_MODE_INVALID));
}
@Test
@@ -1124,7 +1130,8 @@ public class ColorDisplayServiceTest {
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
- eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), any(),
+ eq(Display.COLOR_MODE_INVALID));
}
@Test
@@ -1140,7 +1147,8 @@ public class ColorDisplayServiceTest {
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
- eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), any(),
+ eq(Display.COLOR_MODE_SRGB));
}
@Test
@@ -1156,7 +1164,8 @@ public class ColorDisplayServiceTest {
setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
startService();
verify(mDisplayTransformManager).setColorMode(
- eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
+ eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), any(),
+ eq(Display.COLOR_MODE_INVALID));
}
@Test
@@ -1164,10 +1173,27 @@ public class ColorDisplayServiceTest {
when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
.thenReturn(new int[] {});
startService();
- verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
+ verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), any(), anyInt());
assertThat(mBinderService.getColorMode()).isEqualTo(-1);
}
+ @Test
+ public void ensureColorModeChangeTriggersRbcReload() {
+ // should set up RBC once at startup
+ startService();
+ reset(mRbcSpy);
+
+ // Make sure RBC is enabled and available for this test
+ doReturn(true).when(mRbcSpy).isAvailable(mContext);
+
+ // When Color Mode changes, RBC needs to re-setup
+ // onDisplayColorModeChanged cancels animations, and should be called in handler thread
+ mCds.mHandler.runWithScissors(
+ () -> mCds.onDisplayColorModeChanged(ColorDisplayManager.COLOR_MODE_NATURAL),
+ 1000);
+ verify(mRbcSpy, times(1)).setUp(eq(mContext), anyBoolean());
+ }
+
/**
* Configures Night display to use a custom schedule.
*
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
index 27f87aae35bb..a7ef5e0afc0e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
@@ -19,6 +19,7 @@ package com.android.server.display.color;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS;
import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE;
import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_DISPLAY_COLOR;
import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_SATURATION;
@@ -51,12 +52,14 @@ public class DisplayTransformManagerTest {
private MockitoSession mSession;
private DisplayTransformManager mDtm;
private float[] mNightDisplayMatrix;
+ private float[] mRbcMatrix;
private HashMap<String, String> mSystemProperties;
@Before
public void setUp() {
mDtm = new DisplayTransformManager();
mNightDisplayMatrix = mDtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
+ mRbcMatrix = mDtm.getColorMatrix(LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS);
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
@@ -81,7 +84,8 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_natural() {
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix, mRbcMatrix,
+ Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo("0" /* managed */);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
@@ -90,7 +94,8 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_boosted() {
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED, mNightDisplayMatrix, mRbcMatrix,
+ Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo("0" /* managed */);
@@ -100,7 +105,8 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_saturated() {
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED, mNightDisplayMatrix, mRbcMatrix,
+ Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo("1" /* unmanaged */);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
@@ -109,7 +115,8 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_automatic() {
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC, mNightDisplayMatrix, mRbcMatrix,
+ Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo("2" /* enhanced */);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
@@ -118,7 +125,7 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_vendor() {
- mDtm.setColorMode(0x100, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(0x100, mNightDisplayMatrix, mRbcMatrix, Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo(Integer.toString(0x100) /* pass-through */);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
@@ -127,7 +134,7 @@ public class DisplayTransformManagerTest {
@Test
public void setColorMode_outOfBounds() {
- mDtm.setColorMode(0x50, mNightDisplayMatrix, -1);
+ mDtm.setColorMode(0x50, mNightDisplayMatrix, mRbcMatrix, Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
.isEqualTo(null);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
@@ -141,7 +148,7 @@ public class DisplayTransformManagerTest {
// Start with a known state, which we expect to remain unmodified
SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE, magicPropertyValue);
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix,
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix, mRbcMatrix,
Display.COLOR_MODE_INVALID);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE))
.isEqualTo(magicPropertyValue);
@@ -155,7 +162,7 @@ public class DisplayTransformManagerTest {
// Start with a known state, which we expect to get modified
SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE, magicPropertyValue);
- mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix,
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix, mRbcMatrix,
testPropertyValue);
assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE))
.isEqualTo(Integer.toString(testPropertyValue));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1efe4707fc11..9e96800ca2e9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -40,6 +41,7 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -283,6 +285,15 @@ public class MockingOomAdjusterTests {
}
}
+ private static void assertNoCpuTime(ProcessRecord app) {
+ assertEquals(0, app.mState.getSetCapability() & PROCESS_CAPABILITY_CPU_TIME);
+ }
+
+ private static void assertCpuTime(ProcessRecord app) {
+ assertEquals(PROCESS_CAPABILITY_CPU_TIME,
+ app.mState.getSetCapability() & PROCESS_CAPABILITY_CPU_TIME);
+ }
+
private static void assertBfsl(ProcessRecord app) {
assertEquals(PROCESS_CAPABILITY_BFSL,
app.mState.getSetCapability() & PROCESS_CAPABILITY_BFSL);
@@ -661,6 +672,7 @@ public class MockingOomAdjusterTests {
// SHORT_SERVICE, timed out already.
s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
+
mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -687,6 +699,51 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ public void testUpdateOomAdjFreezeState_bindingFromShortFgs() {
+ // Setting up a started short FGS within app1.
+ final ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
+ s.appInfo = new ApplicationInfo();
+ mProcessStateController.setStartRequested(s, true);
+ s.isForeground = true;
+ s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ mProcessStateController.setHostProcess(s, app);
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ mProcessStateController.startService(app.mServices, s);
+ app.mState.setLastTopTime(SystemClock.uptimeMillis()
+ - mService.mConstants.TOP_TO_FGS_GRACE_DURATION);
+
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ // App1 with short service binds to app2
+ bindService(app2, app, null, null, 0, mock(IBinder.class));
+
+ setProcessesToLru(app, app2);
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ assertCpuTime(app2);
+
+ // Timeout the short FGS.
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis()
+ - mService.mConstants.mShortFgsTimeoutDuration
+ - mService.mConstants.mShortFgsProcStateExtraWaitDuration);
+ mService.mServices.onShortFgsProcstateTimeout(s);
+ // mService is a mock, but this verifies that the timeout would trigger an update.
+ verify(mService).updateOomAdjLocked(app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+ updateOomAdj(app);
+
+ assertNoCpuTime(app);
+ assertNoCpuTime(app2);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoOne_OverlayUi() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
@@ -3142,11 +3199,19 @@ public class MockingOomAdjusterTests {
assertEquals(true, app.getUidRecord().isSetAllowListed());
assertFreezeState(app, false);
assertFreezeState(app2, false);
+ if (Flags.useCpuTimeCapability()) {
+ assertCpuTime(app);
+ assertCpuTime(app2);
+ }
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertFreezeState(app, true);
assertFreezeState(app2, true);
+ if (Flags.useCpuTimeCapability()) {
+ assertNoCpuTime(app);
+ assertNoCpuTime(app2);
+ }
}
@SuppressWarnings("GuardedBy")
@@ -3171,6 +3236,11 @@ public class MockingOomAdjusterTests {
assertFreezeState(app, false);
assertFreezeState(app2, false);
assertFreezeState(app3, false);
+ if (Flags.useCpuTimeCapability()) {
+ assertCpuTime(app);
+ assertCpuTime(app2);
+ assertCpuTime(app3);
+ }
// Remove app1 from allowlist.
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
@@ -3179,6 +3249,11 @@ public class MockingOomAdjusterTests {
assertFreezeState(app, true);
assertFreezeState(app2, false);
assertFreezeState(app3, false);
+ if (Flags.useCpuTimeCapability()) {
+ assertNoCpuTime(app);
+ assertCpuTime(app2);
+ assertCpuTime(app3);
+ }
// Now remove app2 from allowlist.
mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
@@ -3187,6 +3262,11 @@ public class MockingOomAdjusterTests {
assertFreezeState(app, true);
assertFreezeState(app2, true);
assertFreezeState(app3, true);
+ if (Flags.useCpuTimeCapability()) {
+ assertNoCpuTime(app);
+ assertNoCpuTime(app2);
+ assertNoCpuTime(app3);
+ }
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 2988c77703b7..7e052dcba3fd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
@@ -64,6 +65,8 @@ import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -326,6 +329,7 @@ public final class ServiceBindingOomAdjPolicyTest {
@Test
@RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ @DisableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
public void testServiceDistinctBindingOomAdjShouldNotFreeze() throws Exception {
// Enable the flags.
mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
@@ -418,6 +422,7 @@ public final class ServiceBindingOomAdjPolicyTest {
@Test
@RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ @DisableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
public void testServiceDistinctBindingOomAdjAllowOomManagement() throws Exception {
// Enable the flags.
mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
@@ -497,6 +502,7 @@ public final class ServiceBindingOomAdjPolicyTest {
@Test
@RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
+ @DisableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
public void testServiceDistinctBindingOomAdjWaivePriority_propagateUnfreeze() throws Exception {
// Enable the flags.
mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
@@ -574,6 +580,50 @@ public final class ServiceBindingOomAdjPolicyTest {
}
@Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_UNFREEZE_BIND_POLICY_FIX,
+ Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY
+ })
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ public void testServiceDistinctBindingOomAdj_propagateCpuTimeCapability() throws Exception {
+ // Note that PROCESS_CAPABILITY_CPU_TIME is special and should be propagated even when
+ // BIND_INCLUDE_CAPABILITIES is not present.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_CPU_TIME, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+
+ // BIND_WAIVE_PRIORITY should not affect propagation of capability CPU_TIME
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_CPU_TIME,
+ TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ atLeastOnce(), atLeastOnce());
+
+ // If both process have the capability, the bind should not need an update but the unbind
+ // is not safe to skip.
+ // Note that this check can fail on future changes that are not related to
+ // PROCESS_CAPABILITY_CPU_TIME and trigger updates but this is important to ensure
+ // efficiency of OomAdjuster.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_CPU_TIME, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_CPU_TIME, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ never(), atLeastOnce());
+ }
+
+ @Test
@RequiresFlagsDisabled(com.android.server.am.Flags.FLAG_UNFREEZE_BIND_POLICY_FIX)
public void testServiceDistinctBindingOomAdjWaivePriority() throws Exception {
// Enable the flags.
@@ -624,6 +674,9 @@ public final class ServiceBindingOomAdjPolicyTest {
// Enable the flags.
mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+ // Note that some capabilities like PROCESS_CAPABILITY_CPU_TIME are special and propagated
+ // regardless of BIND_INCLUDE_CAPABILITIES. We don't test for them here.
+
// Verify that there should be 0 oom adj update
// because we didn't specify the "BIND_INCLUDE_CAPABILITIES"
performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 428e438443cd..4b2e850d08e7 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -188,6 +188,7 @@ public class HintManagerServiceTest {
mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
mSupportInfo.headroom.isGpuSupported = true;
mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
+ mSupportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
return mSupportInfo;
}
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 8024915692aa..71a2651a0f14 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -16,7 +16,12 @@
package com.android.server;
-import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
+import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
+import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
+import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -24,19 +29,27 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
import android.test.mock.MockContentResolver;
import android.testing.TestableLooper;
@@ -55,6 +68,7 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -62,6 +76,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link GestureLauncherService}.
@@ -90,9 +106,32 @@ public class GestureLauncherServiceTest {
private @Mock TelecomManager mTelecomManager;
private @Mock MetricsLogger mMetricsLogger;
@Mock private UiEventLogger mUiEventLogger;
+ @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
private MockContentResolver mContentResolver;
private GestureLauncherService mGestureLauncherService;
+ private Context mInstrumentationContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ private static final String LAUNCH_TEST_WALLET_ACTION = "LAUNCH_TEST_WALLET_ACTION";
+ private static final String LAUNCH_FALLBACK_ACTION = "LAUNCH_FALLBACK_ACTION";
+ private PendingIntent mGesturePendingIntent =
+ PendingIntent.getBroadcast(
+ mInstrumentationContext,
+ 0,
+ new Intent(LAUNCH_TEST_WALLET_ACTION),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+
+ private PendingIntent mFallbackPendingIntent =
+ PendingIntent.getBroadcast(
+ mInstrumentationContext,
+ 0,
+ new Intent(LAUNCH_FALLBACK_ACTION),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@BeforeClass
public static void oneTimeInitialization() {
if (Looper.myLooper() == null) {
@@ -115,9 +154,49 @@ public class GestureLauncherServiceTest {
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
+ when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
+
+ mGestureLauncherService =
+ new GestureLauncherService(
+ mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
+
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ }
+
+ private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
+ IntentFilter filter = new IntentFilter(action);
+ WalletLaunchedReceiver receiver = new WalletLaunchedReceiver();
+ mInstrumentationContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
+ return receiver;
+ }
+
+ /**
+ * A simple {@link BroadcastReceiver} implementation that counts down a {@link CountDownLatch}
+ * when a matching message is received
+ */
+ private static final class WalletLaunchedReceiver extends BroadcastReceiver {
+ private static final int TIMEOUT_SECONDS = 3;
- mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
- mUiEventLogger);
+ private final CountDownLatch mLatch;
+
+ WalletLaunchedReceiver() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mLatch.countDown();
+ context.unregisterReceiver(this);
+ }
+
+ Boolean waitUntilShown() {
+ try {
+ return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
}
@Test
@@ -134,37 +213,123 @@ public class GestureLauncherServiceTest {
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(false);
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(0);
- assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(false);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(1);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ }
assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(
+ mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled() {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertTrue(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
+ withDoubleTapPowerEnabledConfigValue(false);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertTrue(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
public void testIsEmergencyGestureSettingEnabled_settingDisabled() {
withEmergencyGestureEnabledConfigValue(true);
withEmergencyGestureEnabledSettingValue(false);
@@ -245,12 +410,9 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
- long eventTime = INITIAL_EVENT_TIME_MILLIS +
- CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ long eventTime = INITIAL_EVENT_TIME_MILLIS + POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
boolean interactive = true;
@@ -284,8 +446,12 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -298,7 +464,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -309,7 +475,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -329,8 +495,12 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -343,7 +513,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -354,7 +524,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -401,7 +571,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -422,10 +592,7 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -437,7 +604,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -450,7 +617,7 @@ public class GestureLauncherServiceTest {
verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
- .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
verify(mUiEventLogger, times(1))
.log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
@@ -470,15 +637,145 @@ public class GestureLauncherServiceTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void
+ testInterceptPowerKeyDown_fiveInboundPresses_walletAndEmergencyEnabled_bothLaunch() {
+ WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+ enableEmergencyGesture();
+ enableWalletGesture();
+
+ // First event
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+ assertTrue(receiver.waitUntilShown());
+
+ // Presses 3 and 4 should not trigger any gesture
+ for (int i = 0; i < 2; i++) {
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
+ }
+
+ // Fifth button press should trigger the emergency flow
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+ verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testInterceptPowerKeyDown_intervalInBoundsWalletPowerGesture() {
+ WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+ enableWalletGesture();
+ enableEmergencyGesture();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+ assertTrue(receiver.waitUntilShown());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testInterceptPowerKeyDown_walletGestureOn_quickAccessWalletServiceUnavailable() {
+ when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
+ WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+ enableWalletGesture();
+
+ // First event
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
+
+ assertFalse(receiver.waitUntilShown());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_walletGestureOn_userSetupIncomplete() {
+ WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
+ enableWalletGesture();
+ withUserSetupCompleteValue(false);
+
+ // First event
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ assertFalse(receiver.waitUntilShown());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testInterceptPowerKeyDown_walletPowerGesture_nullPendingIntent() {
+ WalletLaunchedReceiver gestureReceiver =
+ registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(null);
+ WalletLaunchedReceiver fallbackReceiver =
+ registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
+ setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
+ enableWalletGesture();
+ enableEmergencyGesture();
+
+ // First event
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
+
+ assertFalse(gestureReceiver.waitUntilShown());
+ assertTrue(fallbackReceiver.waitUntilShown());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testInterceptPowerKeyDown_walletPowerGesture_intervalOutOfBounds() {
+ WalletLaunchedReceiver gestureReceiver =
+ registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
+ setUpGetGestureTargetActivityPendingIntent(null);
+ WalletLaunchedReceiver fallbackReceiver =
+ registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
+ setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
+ enableWalletGesture();
+ enableEmergencyGesture();
+
+ // First event
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ eventTime += interval;
+ sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
+
+ assertFalse(gestureReceiver.waitUntilShown());
+ assertFalse(fallbackReceiver.waitUntilShown());
+ }
+
+ @Test
public void
testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- withEmergencyGestureEnabledConfigValue(true);
- withEmergencyGestureEnabledSettingValue(true);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- mGestureLauncherService.updateEmergencyGestureEnabled();
- withUserSetupCompleteValue(true);
+ enableCameraGesture();
+ enableEmergencyGesture();
// First button press does nothing
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -491,7 +788,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 2nd button triggers camera
eventTime += interval;
@@ -507,7 +804,7 @@ public class GestureLauncherServiceTest {
verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
- .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
verify(mUiEventLogger, times(1))
.log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
@@ -580,7 +877,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture (camera gesture disabled)
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -634,7 +931,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture, but intercepts action.
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -737,7 +1034,7 @@ public class GestureLauncherServiceTest {
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -765,7 +1062,7 @@ public class GestureLauncherServiceTest {
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -916,7 +1213,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
@@ -947,9 +1244,7 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -962,7 +1257,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -973,7 +1268,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -995,9 +1290,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1009,7 +1302,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1020,7 +1313,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1042,9 +1335,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1067,7 +1358,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1087,8 +1378,12 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerGestureEnableSettingValue(false);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1101,7 +1396,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1112,7 +1407,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1146,7 +1441,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1156,7 +1451,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1202,7 +1497,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1223,10 +1518,7 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1238,7 +1530,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1250,7 +1542,7 @@ public class GestureLauncherServiceTest {
verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
- .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
verify(mUiEventLogger, times(1))
.log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
@@ -1272,9 +1564,7 @@ public class GestureLauncherServiceTest {
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1287,7 +1577,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1298,7 +1588,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1332,7 +1622,7 @@ public class GestureLauncherServiceTest {
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1343,7 +1633,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1365,9 +1655,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1390,7 +1678,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1414,7 +1702,7 @@ public class GestureLauncherServiceTest {
* @return last event time.
*/
private long triggerEmergencyGesture() {
- return triggerEmergencyGesture(CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
+ return triggerEmergencyGesture(POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
}
/**
@@ -1473,6 +1761,27 @@ public class GestureLauncherServiceTest {
.thenReturn(enableConfigValue);
}
+ private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
+ when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
+ .thenReturn(enable);
+ }
+
+ private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+ enable ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void withDefaultDoubleTapPowerGestureAction(int action) {
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+ action,
+ UserHandle.USER_CURRENT);
+ }
+
private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
when(mResources.getBoolean(
com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1510,4 +1819,72 @@ public class GestureLauncherServiceTest {
userSetupCompleteValue,
UserHandle.USER_CURRENT);
}
+
+ private void setUpGetGestureTargetActivityPendingIntent(PendingIntent pendingIntent) {
+ doAnswer(
+ invocation -> {
+ QuickAccessWalletClient.GesturePendingIntentCallback callback =
+ (QuickAccessWalletClient.GesturePendingIntentCallback)
+ invocation.getArguments()[1];
+ callback.onGesturePendingIntentRetrieved(pendingIntent);
+ return null;
+ })
+ .when(mQuickAccessWalletClient)
+ .getGestureTargetActivityPendingIntent(any(), any());
+ }
+
+ private void setUpWalletFallbackPendingIntent(PendingIntent pendingIntent) {
+ doAnswer(
+ invocation -> {
+ QuickAccessWalletClient.WalletPendingIntentCallback callback =
+ (QuickAccessWalletClient.WalletPendingIntentCallback)
+ invocation.getArguments()[1];
+ callback.onWalletPendingIntentRetrieved(pendingIntent);
+ return null;
+ })
+ .when(mQuickAccessWalletClient)
+ .getWalletPendingIntent(any(), any());
+ }
+
+ private void enableWalletGesture() {
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerEnabledConfigValue(true);
+
+ mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
+ }
+
+ private void enableEmergencyGesture() {
+ withEmergencyGestureEnabledConfigValue(true);
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
+ withUserSetupCompleteValue(true);
+ }
+
+ private void enableCameraGesture() {
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerEnabledConfigValue(true);
+ withDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ }
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
+ }
+
+ private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
+ long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
+ KeyEvent keyEvent =
+ new KeyEvent(
+ IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted =
+ mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched);
+ assertEquals(intercepted, expectedIntercept);
+ assertEquals(outLaunched.value, expectedOutLaunchedValue);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java
index d69e47684f8b..9b878b349618 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerCacheTest.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.LocaleManager;
@@ -26,6 +27,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.multiuser.Flags;
+import android.os.Bundle;
import android.os.LocaleList;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -261,6 +263,162 @@ public final class UserManagerCacheTest {
assertThat(um.getUserName()).isEqualTo(newName);
}
+
+ @MediumTest
+ @Test
+ public void testDefaultRestrictionsApplied() throws Exception {
+ final UserInfo userInfo = mUserManager.createUser("Useroid",
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ mUsersToRemove.add(userInfo.id);
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_FULL_SECONDARY);
+ final Bundle expectedRestrictions = userTypeDetails.getDefaultRestrictions();
+ // Note this can fail if DO unset those restrictions.
+ for (String restriction : expectedRestrictions.keySet()) {
+ if (expectedRestrictions.getBoolean(restriction)) {
+ assertThat(mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)))
+ .isTrue();
+ // Test cached value
+ assertThat(mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)))
+ .isTrue();
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testSetDefaultGuestRestrictions() {
+ final Bundle origRestrictions = mUserManager.getDefaultGuestRestrictions();
+ try {
+ final boolean isFunDisallowed = origRestrictions.getBoolean(UserManager.DISALLOW_FUN,
+ false);
+ final UserInfo guest1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ assertThat(guest1).isNotNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest1.getUserHandle())).isEqualTo(isFunDisallowed);
+ removeUser(guest1.id, true);
+ // Cache return false after user was removed
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest1.getUserHandle())).isFalse();
+
+ Bundle restrictions = new Bundle();
+ restrictions.putBoolean(UserManager.DISALLOW_FUN, !isFunDisallowed);
+ mUserManager.setDefaultGuestRestrictions(restrictions);
+ UserInfo guest2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
+ assertThat(guest2).isNotNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest2.getUserHandle())).isNotEqualTo(isFunDisallowed);
+ removeUser(guest2.id, true);
+ assertThat(mUserManager.getUserInfo(guest2.id)).isNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest2.getUserHandle())).isFalse();
+ } finally {
+ mUserManager.setDefaultGuestRestrictions(origRestrictions);
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testCacheInvalidatedAfterUserAddedOrRemoved() {
+ final Bundle origRestrictions = mUserManager.getDefaultGuestRestrictions();
+ try {
+ final boolean isFunDisallowed = origRestrictions.getBoolean(UserManager.DISALLOW_FUN,
+ false);
+ final UserInfo guest1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ assertThat(guest1).isNotNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest1.getUserHandle())).isEqualTo(isFunDisallowed);
+ removeUser(guest1.id, true);
+
+ Bundle restrictions = new Bundle();
+ restrictions.putBoolean(UserManager.DISALLOW_FUN, !isFunDisallowed);
+ mUserManager.setDefaultGuestRestrictions(restrictions);
+ int latest_id = guest1.id;
+ // Cache removed id and few next ids.
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ UserHandle.of(latest_id))).isFalse();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ UserHandle.of(latest_id + 1))).isFalse();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ UserHandle.of(latest_id + 2))).isFalse();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ UserHandle.of(latest_id + 3))).isFalse();
+
+ UserInfo guest2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
+ assertThat(guest2).isNotNull();
+ // Cache was invalidated after user was added
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest2.getUserHandle())).isTrue();
+ removeUser(guest2.id, true);
+ assertThat(mUserManager.getUserInfo(guest2.id)).isNull();
+ // Cache was invalidated after user was removed
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest2.getUserHandle())).isFalse();
+ } finally {
+ mUserManager.setDefaultGuestRestrictions(origRestrictions);
+ }
+ }
+
+
+ @MediumTest
+ @Test
+ public void testAddRemoveUsersAndRestrictions() {
+ try {
+ final UserInfo userInfo = mUserManager.createUser("Useroid",
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ mUsersToRemove.add(userInfo.id);
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ userInfo.getUserHandle())).isFalse();
+ mUserManager.setUserRestriction(UserManager.DISALLOW_FUN, true,
+ userInfo.getUserHandle());
+
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ userInfo.getUserHandle())).isTrue();
+ removeUser(userInfo.id, true);
+ assertThat(mUserManager.getUserSerialNumber(userInfo.id)).isEqualTo(-1);
+ assertThat(mUserManager.getUserInfo(userInfo.id)).isNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ userInfo.getUserHandle())).isFalse();
+ } catch (java.lang.Exception e) {
+ }
+ }
+
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @MediumTest
+ @Test
+ public void testDefaultUserRestrictionsForPrivateProfile() {
+ assumeTrue(mUserManager.canAddPrivateProfile());
+ final int currentUserId = ActivityManager.getCurrentUser();
+ UserInfo privateProfileInfo = null;
+ try {
+ privateProfileInfo = mUserManager.createProfileForUser(
+ "Private", UserManager.USER_TYPE_PROFILE_PRIVATE, 0, currentUserId, null);
+ assertThat(privateProfileInfo).isNotNull();
+ } catch (Exception e) {
+ fail("Creation of private profile failed due to " + e.getMessage());
+ }
+ assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle());
+ // Assert cached values
+ assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle());
+ }
+
+ private void assertDefaultPrivateProfileRestrictions(UserHandle userHandle) {
+ Bundle defaultPrivateProfileRestrictions =
+ UserTypeFactory.getDefaultPrivateProfileRestrictions();
+ for (String restriction : defaultPrivateProfileRestrictions.keySet()) {
+ assertThat(mUserManager.hasUserRestrictionForUser(restriction, userHandle)).isTrue();
+ }
+ }
+
private void assumeManagedUsersSupported() {
// In Automotive, if headless system user is enabled, a managed user cannot be created
// under a primary user.
@@ -270,9 +428,23 @@ public final class UserManagerCacheTest {
}
private void removeUser(int userId) {
+ removeUser(userId, false);
+ }
+
+ private void removeUser(int userId, boolean waitForCompleteRemoval) {
mUserManager.removeUser(userId);
mUserRemovalWaiter.waitFor(userId);
mUsersToRemove.remove(userId);
+ if (waitForCompleteRemoval) {
+ int serialNumber = mUserManager.getUserSerialNumber(userId);
+ int timeout = REMOVE_USER_TIMEOUT_SECONDS * 5; // called every 200ms
+ // Wait for the user to be removed from memory
+ while (serialNumber > 0 && timeout > 0) {
+ sleep(200);
+ timeout--;
+ serialNumber = mUserManager.getUserSerialNumber(userId);
+ }
+ }
}
private boolean isAutomotive() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 661d07e09f99..5cb741fe81d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2472,11 +2472,14 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
assertEquals(0, activity.getChildCount());
- final WindowState win1 = createWindow(null, TYPE_APPLICATION, activity, "win1");
- final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
- "startingWin");
- final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "baseWin");
- final WindowState win4 = createWindow(null, TYPE_APPLICATION, activity, "win4");
+ final WindowState win1 = newWindowBuilder("app1", TYPE_APPLICATION).setWindowToken(
+ activity).build();
+ final WindowState startingWin = newWindowBuilder("startingWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(activity).build();
+ final WindowState baseWin = newWindowBuilder("baseWin",
+ TYPE_BASE_APPLICATION).setWindowToken(activity).build();
+ final WindowState win4 = newWindowBuilder("win4", TYPE_APPLICATION).setWindowToken(
+ activity).build();
// Should not contain the windows that were added above.
assertEquals(4, activity.getChildCount());
@@ -2499,14 +2502,17 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
assertNull(activity.findMainWindow());
- final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1");
- final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window11");
- final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window12");
+ final WindowState window1 = newWindowBuilder("window1",
+ TYPE_BASE_APPLICATION).setWindowToken(activity).build();
+ final WindowState window11 = newWindowBuilder("window11", FIRST_SUB_WINDOW).setParent(
+ window1).setWindowToken(activity).build();
+ final WindowState window12 = newWindowBuilder("window12", FIRST_SUB_WINDOW).setParent(
+ window1).setWindowToken(activity).build();
assertEquals(window1, activity.findMainWindow());
window1.mAnimatingExit = true;
assertEquals(window1, activity.findMainWindow());
- final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, activity,
- "window2");
+ final WindowState window2 = newWindowBuilder("window2",
+ TYPE_APPLICATION_STARTING).setWindowToken(activity).build();
assertEquals(window2, activity.findMainWindow());
activity.removeImmediately();
}
@@ -2651,8 +2657,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testStuckExitingWindow() {
- final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
- "closingWindow");
+ final WindowState closingWindow = newWindowBuilder("closingWindow",
+ FIRST_APPLICATION_WINDOW).build();
closingWindow.mAnimatingExit = true;
closingWindow.mRemoveOnExit = true;
closingWindow.mActivityRecord.commitVisibility(
@@ -3313,7 +3319,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
makeWindowVisibleAndDrawn(app, mImeWindow);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
@@ -3341,7 +3347,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
mImeWindow, null, null);
@@ -3385,8 +3391,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
throws RemoteException {
- final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
- final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+ final WindowState app1 = newWindowBuilder("app1", TYPE_APPLICATION).build();
+ final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).build();
mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
mImeWindow, null, null);
@@ -3430,7 +3436,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testImeInsetsFrozenFlag_multiWindowActivities() {
final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent);
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).setWindowToken(
+ imeToken).build();
makeWindowVisibleAndDrawn(ime);
// Create a split-screen root task with activity1 and activity 2.
@@ -3451,8 +3458,10 @@ public class ActivityRecordTests extends WindowTestsBase {
activity1.mImeInsetsFrozenUntilStartInput = true;
activity2.mImeInsetsFrozenUntilStartInput = true;
- final WindowState app1 = createWindow(null, TYPE_APPLICATION, activity1, "app1");
- final WindowState app2 = createWindow(null, TYPE_APPLICATION, activity2, "app2");
+ final WindowState app1 = newWindowBuilder("app1", TYPE_APPLICATION).setWindowToken(
+ activity1).build();
+ final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).setWindowToken(
+ activity2).build();
makeWindowVisibleAndDrawn(app1, app2);
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
@@ -3481,7 +3490,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
makeWindowVisibleAndDrawn(app);
// Put the activity in close transition.
@@ -3508,7 +3517,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testInClosingAnimation_visibilityCommitted_hideSurface() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
makeWindowVisibleAndDrawn(app);
app.mActivityRecord.prepareSurfaces();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index f339d292ed82..429a396ad997 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -92,7 +92,7 @@ import java.util.concurrent.TimeUnit;
* Tests for the {@link DragDropController} class.
*
* Build/Install/Run:
- * atest WmTests:DragDropControllerTests
+ * atest WmTests:DragDropControllerTests
*/
@SmallTest
@Presubmit
@@ -146,12 +146,12 @@ public class DragDropControllerTests extends WindowTestsBase {
*/
private WindowState createDropTargetWindow(String name, int ownerId) {
final Task task = new TaskBuilder(mSupervisor).setUserId(ownerId).build();
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
- .setUseProcess(mProcess).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).setUseProcess(
+ mProcess).build();
// Use a new TestIWindow so we don't collect events for other windows
- final WindowState window = createWindow(
- null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
+ final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, activity, name,
+ ownerId, false, new TestIWindow());
InputChannel channel = new InputChannel();
window.openInputChannel(channel);
window.mHasSurface = true;
@@ -173,12 +173,11 @@ public class DragDropControllerTests extends WindowTestsBase {
@Before
public void setUp() throws Exception {
mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
- mProcess = mSystemServicesTestRule.addProcess(TEST_PACKAGE, "testProc",
- TEST_PID, TEST_UID);
+ mProcess = mSystemServicesTestRule.addProcess(TEST_PACKAGE, "testProc", TEST_PID, TEST_UID);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.startDragAndDrop(any(IBinder.class),
- any(IBinder.class))).thenReturn(true);
+ when(mWm.mInputManager.startDragAndDrop(any(IBinder.class), any(IBinder.class))).thenReturn(
+ true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -286,16 +285,15 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify the start-drag event is sent for the local and global intercept window
// but not the other window
assertTrue(nonLocalWindowDragEvents.isEmpty());
- assertTrue(localWindowDragEvents.get(0).getAction()
- == ACTION_DRAG_STARTED);
+ assertTrue(localWindowDragEvents.get(0).getAction() == ACTION_DRAG_STARTED);
assertTrue(globalInterceptWindowDragEvents.get(0).getAction()
== ACTION_DRAG_STARTED);
// Verify that only the global intercept window receives the clip data with the
// resolved activity info for the drag
assertNull(localWindowDragEvents.get(0).getClipData());
- assertTrue(globalInterceptWindowDragEvents.get(0).getClipData()
- .willParcelWithActivityInfo());
+ assertTrue(globalInterceptWindowDragEvents.get(
+ 0).getClipData().willParcelWithActivityInfo());
mTarget.reportDropWindow(globalInterceptWindow.mInputChannelToken, 0, 0);
mTarget.handleMotionEvent(false, 0, 0);
@@ -330,9 +328,8 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify the start-drag event has the drag flags
final DragEvent dragEvent = dragEvents.get(0);
assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED);
- assertTrue(dragEvent.getDragFlags() ==
- (View.DRAG_FLAG_GLOBAL
- | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
+ assertTrue(dragEvent.getDragFlags() == (View.DRAG_FLAG_GLOBAL
+ | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
try {
mTarget.mDeferDragStateClosed = true;
@@ -340,9 +337,8 @@ public class DragDropControllerTests extends WindowTestsBase {
// // Verify the drop event does not have the drag flags
mTarget.handleMotionEvent(false, 0, 0);
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
- assertTrue(dropEvent.getDragFlags() ==
- (View.DRAG_FLAG_GLOBAL
- | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
+ assertTrue(dropEvent.getDragFlags() == (View.DRAG_FLAG_GLOBAL
+ | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
mTarget.reportDropResult(iwindow, true);
} finally {
@@ -385,16 +381,15 @@ public class DragDropControllerTests extends WindowTestsBase {
data.putExtra(Intent.EXTRA_USER, user);
}
final ClipData clipData = new ClipData(
- new ClipDescription("drag", new String[] {
- MIMETYPE_APPLICATION_ACTIVITY}),
+ new ClipDescription("drag", new String[]{MIMETYPE_APPLICATION_ACTIVITY}),
new ClipData.Item(data));
return clipData;
}
@Test
public void testValidateAppShortcutArguments() {
- doReturn(PERMISSION_GRANTED).when(mWm.mContext)
- .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
+ doReturn(PERMISSION_GRANTED).when(mWm.mContext).checkCallingOrSelfPermission(
+ eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
try {
session.validateAndResolveDragMimeTypeExtras(
@@ -414,8 +409,8 @@ public class DragDropControllerTests extends WindowTestsBase {
}
try {
session.validateAndResolveDragMimeTypeExtras(
- createClipDataForShortcut("test_package", "test_shortcut_id", null),
- TEST_UID, TEST_PID, TEST_PACKAGE);
+ createClipDataForShortcut("test_package", "test_shortcut_id", null), TEST_UID,
+ TEST_PID, TEST_PACKAGE);
fail("Expected failure without package name");
} catch (IllegalArgumentException e) {
// Expected failure
@@ -424,8 +419,8 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testValidateProfileAppShortcutArguments_notCallingUid() {
- doReturn(PERMISSION_GRANTED).when(mWm.mContext)
- .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
+ doReturn(PERMISSION_GRANTED).when(mWm.mContext).checkCallingOrSelfPermission(
+ eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class);
final Intent[] shortcutIntents = new Intent[1];
@@ -438,10 +433,9 @@ public class DragDropControllerTests extends WindowTestsBase {
ArgumentCaptor<Integer> callingUser = ArgumentCaptor.forClass(Integer.class);
session.validateAndResolveDragMimeTypeExtras(
createClipDataForShortcut("test_package", "test_shortcut_id",
- mock(UserHandle.class)),
- TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE);
- verify(shortcutService).createShortcutIntents(callingUser.capture(), any(),
- any(), any(), anyInt(), anyInt(), anyInt());
+ mock(UserHandle.class)), TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE);
+ verify(shortcutService).createShortcutIntents(callingUser.capture(), any(), any(), any(),
+ anyInt(), anyInt(), anyInt());
assertTrue(callingUser.getValue() == UserHandle.getUserId(TEST_PROFILE_UID));
}
@@ -458,20 +452,19 @@ public class DragDropControllerTests extends WindowTestsBase {
data.putExtra(Intent.EXTRA_USER, user);
}
final ClipData clipData = new ClipData(
- new ClipDescription("drag", new String[] {
- MIMETYPE_APPLICATION_SHORTCUT}),
+ new ClipDescription("drag", new String[]{MIMETYPE_APPLICATION_SHORTCUT}),
new ClipData.Item(data));
return clipData;
}
@Test
public void testValidateAppTaskArguments() {
- doReturn(PERMISSION_GRANTED).when(mWm.mContext)
- .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
+ doReturn(PERMISSION_GRANTED).when(mWm.mContext).checkCallingOrSelfPermission(
+ eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
try {
final ClipData clipData = new ClipData(
- new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }),
+ new ClipDescription("drag", new String[]{MIMETYPE_APPLICATION_TASK}),
new ClipData.Item(new Intent()));
session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID, TEST_PID,
@@ -496,8 +489,8 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testValidateFlagsWithPermission() {
- doReturn(PERMISSION_GRANTED).when(mWm.mContext)
- .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
+ doReturn(PERMISSION_GRANTED).when(mWm.mContext).checkCallingOrSelfPermission(
+ eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
try {
session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
@@ -533,8 +526,8 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify the DRAG_ENDED event does NOT include the drag surface
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
- assertTrue(dragEvents.get(dragEvents.size() - 1).getAction()
- == ACTION_DRAG_ENDED);
+ assertTrue(
+ dragEvents.get(dragEvents.size() - 1).getAction() == ACTION_DRAG_ENDED);
assertTrue(dropEvent.getDragSurface() == null);
});
}
@@ -564,8 +557,8 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify the DRAG_ENDED event includes the drag surface
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
- assertTrue(dragEvents.get(dragEvents.size() - 1).getAction()
- == ACTION_DRAG_ENDED);
+ assertTrue(
+ dragEvents.get(dragEvents.size() - 1).getAction() == ACTION_DRAG_ENDED);
assertTrue(dropEvent.getDragSurface() != null);
});
}
@@ -591,18 +584,18 @@ public class DragDropControllerTests extends WindowTestsBase {
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
ClipData.newPlainText("label", "Test"), () -> {
- // Trigger an unhandled drop and verify the global drag listener was called
- mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
- mTarget.reportDropResult(mWindow.mClient, false);
- mTarget.onUnhandledDropCallback(true);
- mToken = null;
- try {
- verify(listener, times(1)).onUnhandledDrop(any(), any());
- } catch (RemoteException e) {
- fail("Failed to verify unhandled drop: " + e);
- }
- });
+ // Trigger an unhandled drop and verify the global drag listener was called
+ mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.reportDropResult(mWindow.mClient, false);
+ mTarget.onUnhandledDropCallback(true);
+ mToken = null;
+ try {
+ verify(listener, times(1)).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
}
@Test
@@ -615,17 +608,17 @@ public class DragDropControllerTests extends WindowTestsBase {
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
ClipData.newPlainText("label", "Test"), () -> {
- // Trigger an unhandled drop and verify the global drag listener was called
- mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
- mTarget.onUnhandledDropCallback(true);
- mToken = null;
- try {
- verify(listener, times(1)).onUnhandledDrop(any(), any());
- } catch (RemoteException e) {
- fail("Failed to verify unhandled drop: " + e);
- }
- });
+ // Trigger an unhandled drop and verify the global drag listener was called
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.onUnhandledDropCallback(true);
+ mToken = null;
+ try {
+ verify(listener, times(1)).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
}
@Test
@@ -636,18 +629,17 @@ public class DragDropControllerTests extends WindowTestsBase {
doReturn(mock(Binder.class)).when(listener).asBinder();
mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
- startDrag(View.DRAG_FLAG_GLOBAL,
- ClipData.newPlainText("label", "Test"), () -> {
- // Trigger an unhandled drop and verify the global drag listener was not called
- mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
- mToken = null;
- try {
- verify(listener, never()).onUnhandledDrop(any(), any());
- } catch (RemoteException e) {
- fail("Failed to verify unhandled drop: " + e);
- }
- });
+ startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+ // Trigger an unhandled drop and verify the global drag listener was not called
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mToken = null;
+ try {
+ verify(listener, never()).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
}
@Test
@@ -660,20 +652,22 @@ public class DragDropControllerTests extends WindowTestsBase {
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
ClipData.newPlainText("label", "Test"), () -> {
- // Trigger an unhandled drop and verify the global drag listener was called
- mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ // Trigger an unhandled drop and verify the global drag listener was called
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
- // Verify that the unhandled drop listener callback timeout has been scheduled
- final Handler handler = mTarget.getHandler();
- assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+ // Verify that the unhandled drop listener callback timeout has been scheduled
+ final Handler handler = mTarget.getHandler();
+ assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
- // Force trigger the timeout and verify that it actually cleans up the drag & timeout
- handler.handleMessage(Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
- assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
- assertFalse(mTarget.dragDropActiveLocked());
- mToken = null;
- });
+ // Force trigger the timeout and verify that it actually cleans up the drag &
+ // timeout
+ handler.handleMessage(
+ Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+ assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+ assertFalse(mTarget.dragDropActiveLocked());
+ mToken = null;
+ });
}
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
@@ -690,15 +684,13 @@ public class DragDropControllerTests extends WindowTestsBase {
private void startDrag(int flag, ClipData data, Runnable r) {
final SurfaceSession appSession = new SurfaceSession();
try {
- final SurfaceControl surface = new SurfaceControl.Builder(appSession)
- .setName("drag surface")
- .setBufferSize(100, 100)
- .setFormat(PixelFormat.TRANSLUCENT)
- .build();
+ final SurfaceControl surface = new SurfaceControl.Builder(appSession).setName(
+ "drag surface").setBufferSize(100, 100).setFormat(
+ PixelFormat.TRANSLUCENT).build();
assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
- mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
- flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
+ mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
+ 0, 0, data);
assertNotNull(mToken);
r.run();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b27025c34d6b..b61dada809d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -89,6 +89,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
+import android.tools.function.Supplier;
import android.util.MergedConfiguration;
import android.util.SparseArray;
import android.view.Display;
@@ -1804,6 +1805,124 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
}
+ protected WindowStateBuilder newWindowBuilder(String name, int type) {
+ return new WindowStateBuilder(name, type, mWm, mDisplayContent, mIWindow,
+ this::getTestSession, this::createWindowToken);
+ }
+
+ /**
+ * Builder for creating new window.
+ */
+ protected static class WindowStateBuilder {
+ private final String mName;
+ private final int mType;
+ private final WindowManagerService mWm;
+ private final DisplayContent mDefaultTargetDisplay;
+ private final Supplier<WindowToken, Session> mSessionSupplier;
+ private final WindowTokenCreator mWindowTokenCreator;
+
+ private int mActivityType = ACTIVITY_TYPE_STANDARD;
+ private IWindow mClientWindow;
+ private boolean mOwnerCanAddInternalSystemWindow = false;
+ private int mOwnerId = 0;
+ private WindowState mParent;
+ private DisplayContent mTargetDisplay;
+ private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ private WindowToken mWindowToken;
+
+ WindowStateBuilder(String name, int type, WindowManagerService windowManagerService,
+ DisplayContent dc, IWindow iWindow, Supplier<WindowToken, Session> sessionSupplier,
+ WindowTokenCreator windowTokenCreator) {
+ mName = name;
+ mType = type;
+ mClientWindow = iWindow;
+ mDefaultTargetDisplay = dc;
+ mSessionSupplier = sessionSupplier;
+ mWindowTokenCreator = windowTokenCreator;
+ mWm = windowManagerService;
+ }
+
+ WindowStateBuilder setActivityType(int activityType) {
+ mActivityType = activityType;
+ return this;
+ }
+
+ WindowStateBuilder setClientWindow(IWindow clientWindow) {
+ mClientWindow = clientWindow;
+ return this;
+ }
+
+ WindowStateBuilder setDisplay(DisplayContent displayContent) {
+ mTargetDisplay = displayContent;
+ return this;
+ }
+
+ WindowStateBuilder setOwnerCanAddInternalSystemWindow(
+ boolean ownerCanAddInternalSystemWindow) {
+ mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
+ return this;
+ }
+
+ WindowStateBuilder setOwnerId(int ownerId) {
+ mOwnerId = ownerId;
+ return this;
+ }
+
+ WindowStateBuilder setParent(WindowState parent) {
+ mParent = parent;
+ return this;
+ }
+
+ WindowStateBuilder setWindowToken(WindowToken token) {
+ mWindowToken = token;
+ return this;
+ }
+
+ WindowStateBuilder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ WindowState build() {
+ SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(mType);
+ attrs.setTitle(mName);
+ attrs.packageName = "test";
+
+ assertFalse(
+ "targetDisplay shouldn't be specified together with windowToken, since"
+ + " windowToken will be derived from targetDisplay.",
+ mWindowToken != null && mTargetDisplay != null);
+
+ if (mWindowToken == null) {
+ if (mTargetDisplay != null) {
+ mWindowToken = mWindowTokenCreator.createWindowToken(mTargetDisplay,
+ mWindowingMode, mActivityType, mType);
+ } else if (mParent != null) {
+ mWindowToken = mParent.mToken;
+ } else {
+ // Use default mDisplayContent as window token.
+ mWindowToken = mWindowTokenCreator.createWindowToken(mDefaultTargetDisplay,
+ mWindowingMode, mActivityType, mType);
+ }
+ }
+
+ final WindowState w = new WindowState(mWm, mSessionSupplier.get(mWindowToken),
+ mClientWindow, mWindowToken, mParent, OP_NONE, attrs, VISIBLE, mOwnerId,
+ UserHandle.getUserId(mOwnerId), mOwnerCanAddInternalSystemWindow);
+ // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
+ // adding it to the token...
+ mWindowToken.addWindow(w);
+ return w;
+ }
+
+ interface WindowTokenCreator {
+ WindowToken createWindowToken(DisplayContent dc, int windowingMode, int activityType,
+ int type);
+ }
+ }
+
static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer {
private final ActivityTaskManagerService mAtm;
private final WindowManagerService mWMService;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index c508fa968b14..ce3cd29a3ce2 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -26,6 +26,7 @@ import android.util.Slog;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.audio.AudioService;
+import com.android.server.usb.flags.Flags;
import java.util.Arrays;
@@ -211,6 +212,9 @@ public final class UsbAlsaDevice {
mIsSelected[direction] = true;
mState[direction] = 0;
startJackDetect();
+ if (direction == OUTPUT && Flags.maximizeUsbAudioVolumeWhenConnecting()) {
+ nativeSetVolume(mCardNum, 1.0f /*volume*/);
+ }
updateWiredDeviceConnectionState(direction, true /*enable*/);
}
@@ -412,5 +416,7 @@ public final class UsbAlsaDevice {
return result;
}
+
+ private native void nativeSetVolume(int card, float volume);
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index a2d0efd1d063..dfbd74c1f3e1 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -21,3 +21,10 @@ flag {
description: "This flag checks if phone is unlocked after boot"
bug: "73654179"
}
+
+flag {
+ name: "maximize_usb_audio_volume_when_connecting"
+ namespace: "usb"
+ description: "This flag maximizes the usb audio volume when it is connected"
+ bug: "245041322"
+}