summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/Android.bp1
-rw-r--r--core/api/current.txt62
-rw-r--r--core/api/system-current.txt119
-rw-r--r--core/api/system-lint-baseline.txt2
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/Android.bp3
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/ActivityThread.java7
-rw-r--r--core/java/android/app/ContextImpl.java18
-rw-r--r--core/java/android/app/GrammaticalInflectionManager.java5
-rw-r--r--core/java/android/app/Notification.java21
-rw-r--r--core/java/android/app/SystemServiceRegistry.java15
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java5
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java23
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/network-policy.aconfig11
-rw-r--r--core/java/android/app/ondeviceintelligence/Content.java90
-rw-r--r--core/java/android/app/ondeviceintelligence/DownloadCallback.java2
-rw-r--r--core/java/android/app/ondeviceintelligence/Feature.java1
-rw-r--r--core/java/android/app/ondeviceintelligence/FeatureDetails.java3
-rw-r--r--core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl5
-rw-r--r--core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl16
-rw-r--r--core/java/android/app/ondeviceintelligence/IResponseCallback.aidl7
-rw-r--r--core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl10
-rw-r--r--core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java198
-rw-r--r--core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java273
-rw-r--r--core/java/android/app/ondeviceintelligence/ProcessingCallback.java71
-rw-r--r--core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java54
-rw-r--r--core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java (renamed from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java)14
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java13
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java26
-rw-r--r--core/java/android/content/pm/ActivityInfo.java7
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl3
-rw-r--r--core/java/android/content/pm/LauncherApps.java13
-rw-r--r--core/java/android/content/res/flags.aconfig2
-rw-r--r--core/java/android/content/rollback/OWNERS4
-rw-r--r--core/java/android/credentials/flags.aconfig10
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java2
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java2
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java74
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java6
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java12
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java18
-rw-r--r--core/java/android/hardware/camera2/ExtensionCaptureRequest.java22
-rw-r--r--core/java/android/hardware/camera2/ExtensionCaptureResult.java22
-rw-r--r--core/java/android/hardware/camera2/extension/AdvancedExtender.java8
-rw-r--r--core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl1
-rw-r--r--core/java/android/hardware/camera2/extension/CameraOutputSurface.java11
-rw-r--r--core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl1
-rw-r--r--core/java/android/hardware/camera2/extension/ExtensionConfiguration.java19
-rw-r--r--core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java6
-rw-r--r--core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java125
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java14
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionUtils.java12
-rw-r--r--core/java/android/hardware/devicestate/DeviceState.java6
-rw-r--r--core/java/android/hardware/location/NanoAppMessage.java6
-rw-r--r--core/java/android/hardware/radio/ProgramList.java4
-rw-r--r--core/java/android/hardware/radio/ProgramSelector.java8
-rw-r--r--core/java/android/hardware/radio/RadioManager.java5
-rw-r--r--core/java/android/service/dreams/IDreamManager.aidl1
-rw-r--r--core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl7
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java88
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java86
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java14
-rw-r--r--core/java/android/service/voice/HotwordDetectionService.java13
-rw-r--r--core/java/android/text/BoringLayout.java83
-rw-r--r--core/java/android/text/DynamicLayout.java342
-rw-r--r--core/java/android/text/Layout.java75
-rw-r--r--core/java/android/text/PrecomputedText.java160
-rw-r--r--core/java/android/text/StaticLayout.java493
-rw-r--r--core/java/android/text/TextLine.java157
-rw-r--r--core/java/android/text/flags/flags.aconfig7
-rw-r--r--core/java/android/util/apk/ApkSignatureVerifier.java59
-rw-r--r--core/java/android/view/HandwritingInitiator.java16
-rw-r--r--core/java/android/view/View.java38
-rw-r--r--core/java/android/view/ViewRootImpl.java102
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java38
-rw-r--r--core/java/android/view/autofill/AutofillManager.java2
-rw-r--r--core/java/android/webkit/WebSettings.java8
-rw-r--r--core/java/android/widget/Editor.java28
-rw-r--r--core/java/android/widget/RemoteViews.java3
-rw-r--r--core/java/android/widget/TextView.java3
-rw-r--r--core/java/android/window/InputTransferToken.java44
-rw-r--r--core/java/android/window/StartingWindowRemovalInfo.java27
-rw-r--r--core/java/android/window/TaskFragmentOperation.java85
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig8
-rw-r--r--core/java/com/android/internal/accessibility/common/MagnificationConstants.java7
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java3
-rw-r--r--core/java/com/android/internal/widget/CachingIconView.java2
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java26
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp13
-rw-r--r--core/jni/android_window_InputTransferToken.cpp138
-rw-r--r--core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp82
-rw-r--r--core/jni/include/android_runtime/android_window_InputTransferToken.h33
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--core/res/res/values-watch/themes_device_defaults.xml92
-rw-r--r--core/res/res/values/attrs_manifest.xml7
-rw-r--r--core/tests/coretests/src/android/app/AutomaticZenRuleTest.java2
-rw-r--r--core/tests/coretests/src/android/app/NotificationChannelGroupTest.java3
-rw-r--r--core/tests/coretests/src/android/app/NotificationChannelTest.java2
-rw-r--r--core/tests/coretests/src/android/app/NotificationHistoryTest.java16
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java7
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java14
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java76
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java27
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java18
-rw-r--r--data/etc/OWNERS1
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt349
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt358
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java68
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt92
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt65
-rw-r--r--libs/hwui/Mesh.cpp48
-rw-r--r--libs/hwui/Mesh.h225
-rw-r--r--libs/hwui/RecordingCanvas.cpp13
-rw-r--r--libs/hwui/RecordingCanvas.h17
-rw-r--r--libs/hwui/SkiaCanvas.cpp4
-rw-r--r--libs/hwui/hwui/Canvas.h2
-rw-r--r--libs/hwui/jni/android_graphics_Mesh.cpp14
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp6
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.h3
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp2
-rw-r--r--location/java/android/location/flags/location.aconfig2
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig2
-rw-r--r--native/android/Android.bp1
-rw-r--r--native/android/input_transfer_token.cpp63
-rw-r--r--native/android/libandroid.map.txt3
-rw-r--r--nfc/Android.bp3
-rw-r--r--packages/CrashRecovery/OWNERS4
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java6
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/RescueParty.java6
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java115
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java (renamed from packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java)2
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/FileUtils.java128
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java (renamed from packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java)2
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java188
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java118
-rw-r--r--packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt (renamed from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt)22
-rw-r--r--packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt2
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt2
-rw-r--r--packages/PackageInstaller/res/values-watch/themes.xml8
-rw-r--r--packages/SettingsLib/DataStore/README.md164
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt113
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java11
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.pngbin8939 -> 8935 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.pngbin8939 -> 8935 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.pngbin9075 -> 9351 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt (renamed from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt)23
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt2
-rw-r--r--packages/SettingsLib/res/layout/dialog_with_icon.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt18
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java4
-rw-r--r--packages/SettingsProvider/res/values/strings.xml10
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java7
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java112
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig32
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt3
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt385
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt22
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt68
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt16
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt34
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt78
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt22
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt24
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt27
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt87
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt4
-rw-r--r--packages/SystemUI/customization/res/values/ids.xml9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt139
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt215
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt332
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt148
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt241
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt15
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml9
-rw-r--r--packages/SystemUI/res/values/strings.xml24
-rw-r--r--packages/SystemUI/res/values/styles.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt446
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt267
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt119
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt237
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt175
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt285
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java250
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt60
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt12
-rw-r--r--packages/Tethering/OWNERS1
-rw-r--r--packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java68
-rw-r--r--ravenwood/README.md4
-rw-r--r--ravenwood/api-maintainers.md2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java140
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java51
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java5
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java5
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java3
-rw-r--r--services/backup/BACKUP_OWNERS5
-rw-r--r--services/companion/java/com/android/server/companion/virtual/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java30
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java16
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java5
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java20
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java112
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java29
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java129
-rw-r--r--services/core/java/com/android/server/crashrecovery/OWNERS3
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java15
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java32
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java30
-rw-r--r--services/core/java/com/android/server/display/HighBrightnessModeController.java10
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java3
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java68
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java3
-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/grammaticalinflection/GrammaticalInflectionService.java16
-rw-r--r--services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java16
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java501
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java86
-rw-r--r--services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java149
-rw-r--r--services/core/java/com/android/server/inputmethod/StartInputHistory.java189
-rw-r--r--services/core/java/com/android/server/inputmethod/StartInputInfo.java98
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java28
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java (renamed from core/java/android/app/ondeviceintelligence/Content.aidl)13
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java489
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java96
-rw-r--r--services/core/java/com/android/server/permission/OWNERS3
-rw-r--r--services/core/java/com/android/server/permission/PermissionManagerLocal.java46
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java15
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerLocal.java44
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java9
-rw-r--r--services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java28
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/rollback/OWNERS4
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java16
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java1
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java25
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java4
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java31
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java83
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java38
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp4
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java5
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java28
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java42
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java206
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java13
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java4
-rw-r--r--services/java/com/android/server/SystemServer.java8
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessCheckingService.kt7
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt10
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt40
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java4
-rw-r--r--services/tests/VpnTests/Android.bp10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt95
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java31
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java12
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java12
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java24
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java178
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java152
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java71
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java141
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java76
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java77
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java3
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java13
-rw-r--r--telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl2
-rw-r--r--telephony/java/android/telephony/data/QualifiedNetworksService.java2
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.pngbin77171 -> 88200 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.pngbin70807 -> 80959 bytes
-rw-r--r--tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.pngbin45677 -> 48907 bytes
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt (renamed from tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt)35
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt2
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java4
543 files changed, 14024 insertions, 4995 deletions
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee14fec4..ace56d42ddd1 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@ java_library {
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"error_prone_annotations",
"framework",
diff --git a/core/api/current.txt b/core/api/current.txt
index f5bd21b084bb..14783775d762 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4482,7 +4482,7 @@ package android.app {
method @CallSuper public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
method @Deprecated public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method @Deprecated public void onBackPressed();
@@ -6849,7 +6849,7 @@ package android.app {
}
public abstract static class Notification.Style {
- ctor public Notification.Style();
+ ctor @Deprecated public Notification.Style();
method public android.app.Notification build();
method protected void checkBuilder();
method protected android.widget.RemoteViews getStandardView(int);
@@ -9602,8 +9602,8 @@ package android.appwidget {
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
method public boolean isRequestPinAppWidgetSupported();
- method public void notifyAppWidgetViewDataChanged(int[], int);
- method public void notifyAppWidgetViewDataChanged(int, int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -19286,11 +19286,11 @@ package android.hardware.camera2 {
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
method public boolean isCaptureProcessProgressAvailable(int);
method public boolean isPostviewAvailable(int);
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
@@ -19890,30 +19890,30 @@ package android.hardware.camera2 {
field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest {
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest {
ctor public ExtensionCaptureRequest();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
- }
-
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult {
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult {
ctor public ExtensionCaptureResult();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
}
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
@@ -57915,7 +57915,7 @@ package android.webkit {
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57961,7 +57961,7 @@ package android.webkit {
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -60157,7 +60157,7 @@ package android.widget {
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
- method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb2a4ac944a9..78ac7749fc89 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2190,14 +2190,6 @@ package android.app.job {
package android.app.ondeviceintelligence {
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
- ctor public Content(@NonNull android.os.Bundle);
- method public int describeContents();
- method @NonNull public android.os.Bundle getData();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2233,11 +2225,11 @@ package android.app.ondeviceintelligence {
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+ method public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2247,35 +2239,14 @@ package android.app.ondeviceintelligence {
field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
}
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
- field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
- field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull String);
+ ctor public OnDeviceIntelligenceException(int);
method public int getErrorCode();
method @NonNull public android.os.PersistableBundle getErrorParams();
- field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2291,10 +2262,28 @@ package android.app.ondeviceintelligence {
field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+ field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+ field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+ method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+ method public void onResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2307,8 +2296,8 @@ package android.app.ondeviceintelligence {
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
- method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+ method public void onPartialResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3338,7 +3327,7 @@ package android.app.wearable {
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -3348,7 +3337,7 @@ package android.app.wearable {
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
- field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2
field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
@@ -4912,7 +4901,6 @@ package android.hardware.camera2.extension {
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
- method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
}
@@ -4923,6 +4911,7 @@ package android.hardware.camera2.extension {
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+ method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
}
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -6203,8 +6192,6 @@ package android.hardware.location {
method public long getNanoAppId();
method public boolean isBroadcastMessage();
method @FlaggedApi("android.chre.flags.reliable_message") public boolean isReliable();
- method @FlaggedApi("android.chre.flags.reliable_message") public void setIsReliable(boolean);
- method @FlaggedApi("android.chre.flags.reliable_message") public void setMessageSequenceNumber(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
}
@@ -6261,7 +6248,7 @@ package android.hardware.radio {
method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void close();
- method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+ method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6313,7 +6300,7 @@ package android.hardware.radio {
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
- field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+ field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6321,8 +6308,8 @@ package android.hardware.radio {
field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
- field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
- field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6375,7 +6362,7 @@ package android.hardware.radio {
field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
- field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+ field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -12956,38 +12943,26 @@ package android.service.ondeviceintelligence {
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
method public abstract void onInferenceServiceConnected();
method public abstract void onInferenceServiceDisconnected();
- method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
- method public int getErrorCode();
- }
-
- public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
- field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e72a061d35a..62fc67b8dedf 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@ Todo: android.Manifest.permission#READ_PEOPLE_DATA:
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
-Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
- Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 892567c6a587..bc45a76d861b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@ package android {
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT";
field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4ef48d..4f96206bfd08 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@ java_library {
"com/android/internal/logging/UiEventLoggerImpl.java",
":statslog-framework-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
static_libs: ["modules-utils-uieventlogger-interface"],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index afbefca0cefe..1cc2d25fb76d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7473,7 +7473,7 @@ public class Activity extends ContextThemeWrapper
* intent.
*/
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
- public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
@NonNull ComponentCaller caller) {
onActivityResult(requestCode, resultCode, data);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf3b465906fa..ae5cacd18aa2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4076,6 +4076,13 @@ public final class ActivityThread extends ClientTransactionHandler
ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
} catch (RemoteException ignored) {}
+ if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+ // InetAddress will cache UnknownHostException failures. If the rules got
+ // updated and the app has network access now, we need to clear the negative
+ // cache to ensure valid dns queries can work immediately.
+ // TODO: b/329133769 - Clear only the negative cache once it is available.
+ InetAddress.clearDnsCache();
+ }
}
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4d55b2..df566db5ba97 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -597,12 +597,18 @@ class ContextImpl extends Context {
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
- if (isCredentialProtectedStorage()
- && !getSystemService(UserManager.class)
- .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
- throw new IllegalStateException("SharedPreferences in credential encrypted "
- + "storage are not available until after user (id "
- + UserHandle.myUserId() + ") is unlocked");
+ if (isCredentialProtectedStorage()) {
+ final UserManager um = getSystemService(UserManager.class);
+ if (um == null) {
+ throw new IllegalStateException("SharedPreferences cannot be accessed "
+ + "if UserManager is not available. "
+ + "(e.g. from inside an isolated process)");
+ }
+ if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+ throw new IllegalStateException("SharedPreferences in "
+ + "credential encrypted storage are not available until after "
+ + "user (id " + UserHandle.myUserId() + ") is unlocked");
+ }
}
}
sp = new SharedPreferencesImpl(file, mode);
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 4ce983f6019b..3e7d66563c5e 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -125,7 +125,10 @@ public class GrammaticalInflectionManager {
/**
* Get the current grammatical gender of privileged application from the encrypted file.
*
- * @return the value of grammatical gender
+ * @return the value of system grammatical gender only if the calling app has the permission,
+ * otherwise throwing an exception.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
*
* @see Configuration#getGrammaticalGender
*/
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79cb09d5baea..7337a7c3b2c3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3586,12 +3586,15 @@ public class Notification implements Parcelable
* Sets the token used for background operations for the pending intents associated with this
* notification.
*
+ * This token is automatically set during deserialization for you, you usually won't need to
+ * call this unless you want to change the existing token, if any.
+ *
* @hide
*/
- public void overrideAllowlistToken(IBinder token) {
- mAllowlistToken = token;
+ public void clearAllowlistToken() {
+ mAllowlistToken = null;
if (publicVersion != null) {
- publicVersion.overrideAllowlistToken(token);
+ publicVersion.clearAllowlistToken();
}
}
@@ -7379,6 +7382,15 @@ public class Notification implements Parcelable
public static abstract class Style {
/**
+ * @deprecated public access to the constructor of Style() is only useful for creating
+ * custom subclasses, but that has actually been impossible due to hidden abstract
+ * methods, so this constructor is now officially deprecated to clarify that this is
+ * intended to be disallowed.
+ */
+ @Deprecated
+ public Style() {}
+
+ /**
* The number of items allowed simulatanously in the remote input history.
* @hide
*/
@@ -7531,6 +7543,9 @@ public class Notification implements Parcelable
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
+ * <p>
+ * Note: Calling build() multiple times returns the same Notification instance,
+ * so reusing a builder to create multiple Notifications is discouraged.
*
* @return the fully constructed Notification.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e17f2d..fa4a4009ebc9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,8 @@ import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
@@ -1589,6 +1591,19 @@ public final class SystemServiceRegistry {
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+ new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+ @Override
+ public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+ return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+ }
+ });
+
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a346f7..9ef8b38666c6 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@ public final class DeviceAdminInfo implements Parcelable {
@FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+ /**
+ * @hide
+ */
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
- private @interface HeadlessDeviceOwnerMode {}
+ public @interface HeadlessDeviceOwnerMode {}
/** @hide */
public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 620bbaf4bbf5..cb4ed058af33 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@ import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@ import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -17526,4 +17528,25 @@ public class DevicePolicyManager {
}
return -1;
}
+
+ /**
+ * @return The headless device owner mode for the current set DO, returns
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+ *
+ * @hide
+ */
+ @DeviceAdminInfo.HeadlessDeviceOwnerMode
+ public int getHeadlessDeviceOwnerMode() {
+ if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ if (mService != null) {
+ try {
+ return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
} \ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c7995..03d0b0f88bc0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@ interface IDevicePolicyManager {
void setMaxPolicyStorageLimit(String packageName, int storageLimit);
int getMaxPolicyStorageLimit(String packageName);
+
+ int getHeadlessDeviceOwnerMode(String callerPackageName);
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 19270199696e..c29ea6d95dcc 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -163,3 +163,13 @@ flag {
description: "Enable UX changes for esim management"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_provisioning_fix_enabled"
+ namespace: "enterprise"
+ description: "Fix provisioning for single-user headless DO"
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 000000000000..88f386f6025d
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+ namespace: "backstage_power"
+ name: "clear_dns_cache_on_network_rules_update"
+ description: "Clears the DNS cache when the network rules update"
+ bug: "237556596"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156fc946..000000000000
--- a/core/java/android/app/ondeviceintelligence/Content.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Represents content sent to and received from the on-device inference service.
- * Can contain a collection of text, image, and binary parts or any combination of these.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public final class Content implements Parcelable {
- //TODO: Improve javadoc after adding validation logic.
- private static final String TAG = "Content";
- private final Bundle mData;
-
- /**
- * Create a content object using a Bundle of only known types that are read-only.
- */
- public Content(@NonNull Bundle data) {
- Objects.requireNonNull(data);
- validateBundleData(data);
- this.mData = data;
- }
-
- /**
- * Returns the Content's data represented as a Bundle.
- */
- @NonNull
- public Bundle getData() {
- return mData;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeBundle(mData);
- }
-
- @Override
- public int describeContents() {
- int mask = 0;
- mask |= mData.describeContents();
- return mask;
- }
-
- @NonNull
- public static final Creator<Content> CREATOR = new Creator<>() {
- @Override
- @NonNull
- public Content createFromParcel(@NonNull Parcel in) {
- return new Content(in.readBundle(getClass().getClassLoader()));
- }
-
- @Override
- @NonNull
- public Content[] newArray(int size) {
- return new Content[size];
- }
- };
-
- private void validateBundleData(Bundle unused) {
- // TODO: Validate there are only known types.
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
index 684c71f9144c..30c6e1924942 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@ public interface DownloadCallback {
}
/**
- * Called when model download via MDD completed. The remote implementation can populate any
+ * 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.
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 4a38c9224a3d..fd0379a046cc 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@ import android.os.PersistableBundle;
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class Feature implements Parcelable {
- // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
private final int mId;
@Nullable
private final String mName;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index f3cbd2621694..44930f2c34f4 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@ public final class FeatureDetails implements Parcelable {
/** 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,
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index aba563f84e1b..8fc269ea6dac 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -16,15 +16,14 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
/**
- * Interface for Download callback to passed onto service implementation,
+ * Interface for Download callback to be passed onto service implementation,
*
* @hide
*/
-oneway interface IDownloadCallback {
+interface IDownloadCallback {
void onDownloadStarted(long bytesToDownload) = 1;
void onDownloadProgress(long bytesDownloaded) = 2;
void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 360a8094723c..0dbe18156904 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -21,7 +21,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
- import android.app.ondeviceintelligence.Content;
+ import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -39,7 +39,7 @@
*
* @hide
*/
- oneway interface IOnDeviceIntelligenceManager {
+ interface IOnDeviceIntelligenceManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void getVersion(in RemoteCallback remoteCallback) = 1;
@@ -53,18 +53,20 @@
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, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ void requestTokenInfo(in Feature feature, in Bundle requestBundle, in ICancellationSignal signal,
in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
- in IResponseCallback responseCallback) = 7;
+ void processRequest(in Feature feature, in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal,
+ in IProcessingSignal signal, in IResponseCallback responseCallback) = 7;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequestStreaming(in Feature feature,
- in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
in IStreamingResponseCallback streamingCallback) = 8;
+
+ String getRemoteServicePackageName() = 9;
}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 0adf305f2920..45963d2af4e6 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -1,8 +1,7 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.Bundle;
import android.os.RemoteCallback;
/**
@@ -11,7 +10,7 @@ import android.os.RemoteCallback;
* @hide
*/
interface IResponseCallback {
- void onSuccess(in Content result) = 1;
+ void onSuccess(in Bundle resultBundle) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 132e53e1ae2e..671abe31ce86 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -1,10 +1,8 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
+import android.os.Bundle;
/**
@@ -13,8 +11,8 @@ import android.os.RemoteCallback;
* @hide
*/
interface IStreamingResponseCallback {
- void onNewContent(in Content result) = 1;
- void onSuccess(in Content result) = 2;
+ 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 Content content, in RemoteCallback contentCallback) = 4;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 000000000000..03ff563a88c0
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,198 @@
+/*
+ * 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.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+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
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @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/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d195c4d52c22..a465e3cbb6ec 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -36,6 +37,7 @@ import android.os.OutcomeReceiver;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.system.OsConstants;
import androidx.annotation.IntDef;
@@ -63,7 +65,7 @@ import java.util.function.LongConsumer;
@SystemApi
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public class OnDeviceIntelligenceManager {
+public final class OnDeviceIntelligenceManager {
/**
* @hide
*/
@@ -118,14 +120,13 @@ public class OnDeviceIntelligenceManager {
@Nullable
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public String getRemoteServicePackageName() {
- String serviceConfigValue = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
- ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
- if (componentName != null) {
- return componentName.getPackageName();
+ String result;
+ try{
+ result = mService.getRemoteServicePackageName();
+ } catch (RemoteException e){
+ throw e.rethrowFromSystemServer();
}
-
- return null;
+ return result;
}
/**
@@ -139,7 +140,7 @@ public class OnDeviceIntelligenceManager {
public void getFeature(
int featureId,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
try {
IFeatureCallback callback =
new IFeatureCallback.Stub() {
@@ -154,7 +155,7 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -173,7 +174,7 @@ public class OnDeviceIntelligenceManager {
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void listFeatures(
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
try {
IListFeaturesCallback callback =
new IListFeaturesCallback.Stub() {
@@ -188,7 +189,7 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -211,7 +212,7 @@ public class OnDeviceIntelligenceManager {
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void getFeatureDetails(@NonNull Feature feature,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
try {
IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
@@ -226,7 +227,7 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
- new OnDeviceIntelligenceManagerException(errorCode,
+ new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
}
};
@@ -243,9 +244,8 @@ public class OnDeviceIntelligenceManager {
*
* 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 #getFeatureStatus} to
- * check
- * on the feature's download status.
+ * 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.
@@ -284,7 +284,7 @@ public class OnDeviceIntelligenceManager {
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> onDownloadCompleted(downloadParams)));
+ () -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -305,7 +305,8 @@ public class OnDeviceIntelligenceManager {
* provided {@link Feature}.
*
* @param feature feature associated with the request.
- * @param request request that contains the content data and associated params.
+ * @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
@@ -313,11 +314,11 @@ public class OnDeviceIntelligenceManager {
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ OnDeviceIntelligenceException> outcomeReceiver) {
try {
ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
@@ -331,7 +332,7 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -357,30 +358,30 @@ public class OnDeviceIntelligenceManager {
* failure.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
+ * @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 responseCallback callback to populate the response content and
+ * @param processingCallback callback to populate the response content and
* associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @Nullable Content request,
+ public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull ProcessingOutcomeReceiver responseCallback) {
+ @NonNull ProcessingCallback processingCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseCallback.onResult(result));
+ callbackExecutor.execute(() -> processingCallback.onResult(result));
});
}
@@ -388,16 +389,16 @@ public class OnDeviceIntelligenceManager {
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> processingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onDataAugmentRequest(content, result -> {
+ () -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
@@ -430,15 +431,15 @@ public class OnDeviceIntelligenceManager {
* 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 StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
- * populate the complete the full response {@link Content} as part of the callback in cases
- * when the final response contains an enhanced aggregation of the Contents already
+ * {@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 that contains the Content data and associated
- * params.
+ * @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
@@ -448,27 +449,27 @@ public class OnDeviceIntelligenceManager {
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
+ @NonNull StreamingProcessingCallback streamingProcessingCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
- public void onNewContent(Content result) {
+ public void onNewContent(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onNewContent(result));
+ () -> streamingProcessingCallback.onPartialResult(result));
});
}
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onResult(result));
+ () -> streamingProcessingCallback.onResult(result));
});
}
@@ -477,18 +478,18 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> streamingProcessingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams)));
});
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> streamingResponseCallback.onDataAugmentRequest(content,
+ () -> streamingProcessingCallback.onDataAugmentRequest(content,
contentResponse -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
@@ -519,7 +520,7 @@ public class OnDeviceIntelligenceManager {
}
- /** Request inference with provided Content and Params. */
+ /** Request inference with provided Bundle and Params. */
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
@@ -530,7 +531,7 @@ public class OnDeviceIntelligenceManager {
*/
public static final int REQUEST_TYPE_PREPARE = 1;
- /** Request Embeddings of the passed-in Content. */
+ /** Request Embeddings of the passed-in Bundle. */
public static final int REQUEST_TYPE_EMBEDDINGS = 2;
/**
@@ -547,154 +548,30 @@ public class OnDeviceIntelligenceManager {
public @interface RequestType {
}
-
- /**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager}.
- */
- public static class OnDeviceIntelligenceManagerException extends Exception {
- /**
- * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
- */
- public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
-
- /**
- * Error code to be used for on device intelligence manager failures.
- *
- * @hide
- */
- @IntDef(
- value = {
- ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface OnDeviceIntelligenceManagerErrorCode {
- }
-
- private final int mErrorCode;
- private final PersistableBundle errorParams;
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode,
- @NonNull PersistableBundle errorParams) {
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- @NonNull
- public PersistableBundle getErrorParams() {
- return errorParams;
- }
- }
-
/**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager#processRequest} or
- * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
+ * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+ * when passed to inference service via Binder IPC. Following restrictions apply :
+ * <ul>
+ * <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 Bitmap} set as {@link Bitmap#setImmutable()}</li>
+ * <li>{@link android.database.CursorWindow}</li>
+ * <li>{@link android.os.ParcelFileDescriptor} opened in
+ * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</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
*/
- public static class OnDeviceIntelligenceManagerProcessingException extends
- OnDeviceIntelligenceManagerException {
-
- 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 of failed processing request.
- *
- * @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
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface ProcessingError {
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorMessage, errorParams);
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorParams);
- }
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface InferenceParams {
}
}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 000000000000..4d936ea45c54
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,71 @@
+/*
+ * 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 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 @InferenceParams 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 @InferenceParams Bundle processedContent,
+ @NonNull Consumer<Bundle> contentConsumer) {
+ contentConsumer.accept(Bundle.EMPTY);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
deleted file mode 100644
index b0b6e1948cf0..000000000000
--- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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.OutcomeReceiver;
-
-import java.util.function.Consumer;
-
-/**
- * Response 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 {@link Content} that was already
- * processed in the remote implementation.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface ProcessingOutcomeReceiver extends
- OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- /**
- * 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 content 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.
- */
- default void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentConsumer) {
- contentConsumer.accept(null);
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b0329496c..41f1807a9b3d 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@ import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
/**
- * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in chunks to provide a async processing behaviour to the caller.
+ * 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 StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
+public interface StreamingProcessingCallback extends ProcessingCallback {
/**
- * Callback that would be invoked when a part of the response i.e. some {@link Content} is
- * already processed and needs to be passed onto the caller.
+ * 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 onNewContent(@NonNull Content content);
+ void onPartialResult(@NonNull @InferenceParams Bundle partialResult);
}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index fd72c491bf16..df6d2a635024 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -95,11 +95,12 @@ public class WearableSensingManager {
/**
* The value of the status code that indicates one or more of the requested events are not
* supported.
+ *
+ * @deprecated WearableSensingManager does not deal with events. Use {@link
+ * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
+ * {@link WearableSensingService}.
*/
- // TODO(b/324635656): Deprecate this status code. Update Javadoc:
- // @deprecated WearableSensingManager does not deal with events. Use {@link
- // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
- // {@link WearableSensingService}.
+ @Deprecated
public static final int STATUS_UNSUPPORTED = 2;
/**
@@ -121,7 +122,6 @@ public class WearableSensingManager {
* The value of the status code that indicates the method called is not supported by the
* implementation of {@link WearableSensingService}.
*/
-
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
@@ -246,7 +246,10 @@ public class WearableSensingManager {
* @param executor Executor on which to run the consumer callback
* @param statusConsumer A consumer that handles the status codes, which is returned
* right after the call.
+ * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
+ * to provide a remote wearable device connection to the WearableSensingService
*/
+ @Deprecated
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
public void provideDataStream(
@NonNull ParcelFileDescriptor parcelFileDescriptor,
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index cda4d89b828f..2c0e035e80c4 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@ public class AppWidgetManager {
*
* @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
- */
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
+ */
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
@@ -873,7 +884,18 @@ public class AppWidgetManager {
*
* @param appWidgetId The AppWidget instance to notify of view data changes.
* @param viewId The collection view id.
- */
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
+ */
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
if (mService == null) {
return;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 88527059b3f9..bd04634ac4f1 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
}
/**
- * Specifies permissions necessary to launch this activity via
- * {@link android.content.Context#startActivity} when passing content URIs. The default value is
- * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
- * activity invocation based on the invoker's permissions.
+ * Specifies permissions necessary to launch this activity when passing content URIs. The
+ * default value is {@code none}, meaning no specific permissions are required. Setting this
+ * attribute restricts activity invocation based on the invoker's permissions.
* @hide
*/
@RequiredContentUriPermission
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa512dae8..55957bf887f8 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@ interface ILauncherApps {
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
List<UserHandle> getUserProfiles();
+
+ /** Saves view capture data to the wm trace directory. */
+ void saveViewCaptureData();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17ce978..3a5383d9537b 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@ public class LauncherApps {
}
/**
+ * Saves view capture data to the default location.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void saveViewCaptureData() {
+ try {
+ mService.saveViewCaptureData();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Unregister a callback, so that it won't be called when LauncherApps dumps.
* @hide
*/
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770d2fc8..7fd0b03b213d 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@ flag {
name: "nine_patch_frro"
namespace: "resource_manager"
description: "Feature flag for creating an frro from a 9-patch"
- bug: "309232726"
+ bug: "296324826"
}
flag {
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 8e5a0d8af550..c328b7c36b8f 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 819107
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS \ No newline at end of file
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 09e59d315428..47edba6a9e56 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -72,14 +72,20 @@ flag {
flag {
namespace: "credential_manager"
- name: "clear_credentials_api_fix_enabled"
+ name: "clear_credentials_fix_enabled"
description: "Fixes bug in clearCredential API that causes indefinite suspension"
bug: "314926460"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
namespace: "credential_manager"
- name: "hybrid_filter_fix_enabled"
+ name: "hybrid_filter_opt_fix_enabled"
description: "Removes capability check from hybrid implementation"
bug: "323923403"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 0208fed6040f..be9f0a0c8d13 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -295,7 +295,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* called.
*
* @param view The customized view information.
- * @return This builder.re
+ * @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dc8f4b448931..238c381fc00f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -6098,7 +6098,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }});
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 749f218b0e6a..6962811ab860 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -142,7 +142,7 @@ public final class CameraExtensionCharacteristics {
/**
* An extension that aims to lock and stabilize a given region or object of interest.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
/**
@@ -180,6 +180,16 @@ public final class CameraExtensionCharacteristics {
EXTENSION_HDR,
EXTENSION_NIGHT};
+ /**
+ * List of synthetic CameraCharacteristics keys that are supported in the extensions.
+ */
+ private static final List<CameraCharacteristics.Key>
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
+ Arrays.asList(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+ CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
+ );
+
private final Context mContext;
private final String mCameraId;
private final Map<String, CameraCharacteristics> mCharacteristicsMap;
@@ -217,14 +227,18 @@ public final class CameraExtensionCharacteristics {
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
- // Per API contract it is assumed that the extension is able to support all
- // camera advertised sizes for a given format in case it doesn't return
- // a valid non-empty size list.
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
- Size[] supportedSizes = streamMap.getOutputSizes(format);
- if ((ret.isEmpty()) && (supportedSizes != null)) {
- ret.addAll(Arrays.asList(supportedSizes));
+
+ if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+ // Per API contract it is assumed that the extension is able to support all
+ // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return
+ // a valid non-empty size list.
+ Size[] supportedSizes = streamMap.getOutputSizes(format);
+ if ((ret.isEmpty()) && (supportedSizes != null)) {
+ ret.addAll(Arrays.asList(supportedSizes));
+ }
}
+
return ret;
}
@@ -549,7 +563,7 @@ public final class CameraExtensionCharacteristics {
public ExtensionConnectionManager() {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -752,7 +766,7 @@ public final class CameraExtensionCharacteristics {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -874,11 +888,17 @@ public final class CameraExtensionCharacteristics {
Class<CameraCharacteristics.Key<?>> keyTyped =
(Class<CameraCharacteristics.Key<?>>) key;
- // Do not include synthetic keys. Including synthetic keys leads to undefined
- // behavior. This causes inclusion of capabilities that may not be supported in
- // camera extensions.
ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
/*includeSynthetic*/ false));
+
+ // Add synthetic keys to the available key list if they are part of the supported
+ // synthetic camera characteristic key list
+ for (CameraCharacteristics.Key charKey :
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
+ if (chars.get(charKey) != null) {
+ ret.add(charKey);
+ }
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension for all available keys! Extension "
@@ -990,6 +1010,7 @@ public final class CameraExtensionCharacteristics {
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1021,8 +1042,9 @@ public final class CameraExtensionCharacteristics {
return generateJpegSupportedSizes(
extenders.second.getSupportedPostviewResolutions(sz),
streamMap);
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
+ // extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1118,16 +1140,16 @@ public final class CameraExtensionCharacteristics {
*
* <p>Device-specific extensions currently support at most three
* multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
- * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
- * supported.</p>
+ * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
+ * may or may not be supported.</p>
*
* @param extension the extension type
* @param format device-specific extension output format
* @return non-modifiable list of available sizes or an empty list if the format is not
* supported.
* @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
- * ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
- * unsupported extension.
+ * ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
+ * ImageFormat.YCBCR_P010; or unsupported extension.
*/
public @NonNull
List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1151,6 +1173,7 @@ public final class CameraExtensionCharacteristics {
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1183,8 +1206,9 @@ public final class CameraExtensionCharacteristics {
} else {
return generateSupportedSizes(null, format, streamMap);
}
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+ // basic extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1213,7 +1237,8 @@ public final class CameraExtensionCharacteristics {
* @return the range of estimated minimal and maximal capture latency in milliseconds
* or null if no capture latency info can be provided
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
- * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+ * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
+ * {@link ImageFormat#YCBCR_P010};
* or unsupported extension.
*/
public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1222,6 +1247,7 @@ public final class CameraExtensionCharacteristics {
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
//No op
break;
default:
@@ -1269,8 +1295,8 @@ public final class CameraExtensionCharacteristics {
// specific and cannot be estimated accurately enough.
return null;
}
- if (format == ImageFormat.JPEG_R) {
- // JpegR/UltraHDR is not supported for basic extensions
+ if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
return null;
}
@@ -1522,7 +1548,7 @@ public final class CameraExtensionCharacteristics {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 9fb561bb4211..7754e328bbf9 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3905,7 +3905,7 @@ public abstract class CameraMetadata<TKey> {
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = 0;
/**
@@ -3913,7 +3913,7 @@ public abstract class CameraMetadata<TKey> {
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = 1;
/**
@@ -3923,7 +3923,7 @@ public abstract class CameraMetadata<TKey> {
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = 2;
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c0db77ca0f05..13d5c7e74e4b 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4335,7 +4335,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -4358,7 +4358,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -4379,7 +4379,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
@@ -4406,7 +4406,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -4428,7 +4428,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -4445,7 +4445,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a01c23d984f4..7145501c718d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5940,7 +5940,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION =
new Key<int[]>("android.efv.paddingRegion", int[].class);
@@ -5961,7 +5961,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION =
new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class);
@@ -5984,7 +5984,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES =
new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class);
@@ -6014,7 +6014,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -6041,7 +6041,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -6064,7 +6064,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -6081,7 +6081,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
@@ -6103,7 +6103,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -6124,7 +6124,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
index 32039c6ec0ba..c33956b59f2f 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
@@ -40,7 +40,7 @@ import com.android.internal.camera.flags.Flags;
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureRequest {
/**
@@ -74,7 +74,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -99,7 +99,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM;
/**
@@ -125,7 +125,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR;
/**
@@ -152,7 +152,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE;
/**
@@ -176,7 +176,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT;
/**
@@ -193,7 +193,7 @@ public final class ExtensionCaptureRequest {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT;
@@ -205,14 +205,14 @@ public final class ExtensionCaptureRequest {
* <p>No stabilization.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF;
/**
* <p>Gimbal stabilization mode.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL;
/**
@@ -221,7 +221,7 @@ public final class ExtensionCaptureRequest {
* stabilization to directionally steady the target region.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED;
-} \ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
index 5c9990975a9b..95feb2fd268a 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
@@ -42,7 +42,7 @@ import com.android.internal.camera.flags.Flags;
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureResult {
/**
@@ -66,7 +66,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION;
/**
@@ -90,7 +90,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION;
/**
@@ -113,7 +113,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES;
/**
@@ -147,7 +147,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -174,7 +174,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE;
/**
@@ -199,7 +199,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM;
/**
@@ -216,7 +216,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT;
/**
@@ -240,7 +240,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT;
/**
@@ -266,7 +266,7 @@ public final class ExtensionCaptureResult {
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
-} \ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 4895f38d7328..8fa09a802aa4 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -61,7 +61,6 @@ public abstract class AdvancedExtender {
private CameraUsageTracker mCameraUsageTracker;
private static final String TAG = "AdvancedExtender";
-
/**
* Initialize a camera extension advanced extender instance.
*
@@ -263,6 +262,13 @@ public abstract class AdvancedExtender {
*
* <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
* a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+ *
+ * <p> Currently, the only synthetic keys supported for override are
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM
+ * should override the respective native keys
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
*/
@FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
@NonNull
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 509bcb8e3d23..5567bed7f128 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -27,6 +27,7 @@ parcelable CameraOutputConfig
int imageFormat;
int capacity;
long usage;
+ long dynamicRangeProfile;
const int TYPE_SURFACE = 0;
const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 53f56bc9f896..001b79499b1a 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -133,15 +133,4 @@ public final class CameraOutputSurface {
@DynamicRangeProfiles.Profile long dynamicRangeProfile) {
mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
}
-
- /**
- * Set the color space. The default colorSpace
- * will be
- * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED}
- * unless explicitly set using this method.
- */
- @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
- public void setColorSpace(int colorSpace) {
- mOutputSurface.colorSpace = colorSpace;
- }
}
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 84ca2b63fbcf..9d46b559ce37 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,4 +25,5 @@ parcelable CameraSessionConfig
CameraMetadataNative sessionParameter;
int sessionTemplateId;
int sessionType;
+ int colorSpace = -1;
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 96c88e660e10..84b7a7fc1349 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.os.IBinder;
import com.android.internal.camera.flags.Flags;
@@ -48,6 +49,7 @@ public class ExtensionConfiguration {
private final int mSessionTemplateId;
private final List<ExtensionOutputConfiguration> mOutputs;
private final CaptureRequest mSessionParameters;
+ private int mColorSpace;
/**
* Initialize an extension configuration instance
@@ -72,6 +74,18 @@ public class ExtensionConfiguration {
mSessionTemplateId = sessionTemplateId;
mOutputs = outputs;
mSessionParameters = sessionParams;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+
+ /**
+ * Set the color space using the ordinal value of a
+ * {@link android.graphics.ColorSpace.Named}.
+ * The default will be -1, indicating an unspecified ColorSpace,
+ * unless explicitly set using this method.
+ */
+ @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
+ public void setColorSpace(int colorSpace) {
+ mColorSpace = colorSpace;
}
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -84,6 +98,11 @@ public class ExtensionConfiguration {
ret.sessionTemplateId = mSessionTemplateId;
ret.sessionType = mSessionType;
ret.outputConfigs = new ArrayList<>(mOutputs.size());
+ if (Flags.extension10Bit()) {
+ ret.colorSpace = mColorSpace;
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
for (ExtensionOutputConfiguration outputConfig : mOutputs) {
ret.outputConfigs.add(outputConfig.getOutputConfig());
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 9dc6d7bf94b3..3a67d6192f5e 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import com.android.internal.camera.flags.Flags;
@@ -79,6 +80,11 @@ public class ExtensionOutputConfiguration {
config.outputId = new OutputConfigId();
config.outputId.id = mOutputConfigId;
config.surfaceGroupId = mSurfaceGroupId;
+ if (Flags.extension10Bit()) {
+ config.dynamicRangeProfile = surface.getDynamicRangeProfile();
+ } else {
+ config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
}
@Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index a7d6caf9d9df..6d9b51cbd003 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.SyncFence;
@@ -49,6 +50,7 @@ import android.hardware.camera2.extension.ParcelCaptureResult;
import android.hardware.camera2.extension.ParcelImage;
import android.hardware.camera2.extension.ParcelTotalCaptureResult;
import android.hardware.camera2.extension.Request;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -62,6 +64,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -97,6 +100,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
private Surface mClientRepeatingRequestSurface;
private Surface mClientCaptureSurface;
private Surface mClientPostviewSurface;
+ private OutputConfiguration mClientRepeatingRequestOutputConfig;
+ private OutputConfiguration mClientCaptureOutputConfig;
+ private OutputConfiguration mClientPostviewOutputConfig;
private CameraCaptureSession mCaptureSession = null;
private ISessionProcessorImpl mSessionProcessor = null;
private final InitializeSessionHandler mInitializeHandler;
@@ -142,8 +148,19 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
for (OutputConfiguration c : config.getOutputConfigurations()) {
if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
- throw new IllegalArgumentException("Unsupported dynamic range profile: " +
- c.getDynamicRangeProfile());
+ if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+ DynamicRangeProfiles dynamicProfiles = extensionChars.get(
+ config.getExtension(),
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+ if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles()
+ .contains(c.getDynamicRangeProfile())) {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
}
if (c.getStreamUseCase() !=
CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) {
@@ -157,12 +174,26 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
config.getExtension(), SurfaceTexture.class);
Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface(
config.getOutputConfigurations(), supportedPreviewSizes);
+ OutputConfiguration repeatingRequestOutputConfig = null;
if (repeatingRequestSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == repeatingRequestSurface) {
+ repeatingRequestOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -171,7 +202,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
}
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
+ OutputConfiguration burstCaptureOutputConfig = null;
if (burstCaptureSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == burstCaptureSurface) {
+ burstCaptureOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
@@ -180,13 +217,14 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
}
Surface postviewSurface = null;
+ OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration();
if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) {
CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
CameraExtensionUtils.querySurface(burstCaptureSurface);
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
@@ -207,8 +245,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
extender.init(cameraId, characteristicsMapNative);
CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
- extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
- burstCaptureSurface, postviewSurface, config.getStateCallback(),
+ extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig,
+ burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
config.getExecutor(), sessionId, token, config.getExtension());
ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
@@ -223,8 +261,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
@NonNull IAdvancedExtenderImpl extender,
@NonNull CameraDeviceImpl cameraDevice,
Map<String, CameraMetadataNative> characteristicsMap,
- @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
- @Nullable Surface postviewSurface,
+ @Nullable OutputConfiguration repeatingRequestOutputConfig,
+ @Nullable OutputConfiguration burstCaptureOutputConfig,
+ @Nullable OutputConfiguration postviewOutputConfig,
@NonNull StateCallback callback, @NonNull Executor executor,
int sessionId,
@NonNull IBinder token,
@@ -235,9 +274,18 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
mCharacteristicsMap = characteristicsMap;
mCallbacks = callback;
mExecutor = executor;
- mClientRepeatingRequestSurface = repeatingRequestSurface;
- mClientCaptureSurface = burstCaptureSurface;
- mClientPostviewSurface = postviewSurface;
+ mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig;
+ mClientCaptureOutputConfig = burstCaptureOutputConfig;
+ mClientPostviewOutputConfig = postviewOutputConfig;
+ if (repeatingRequestOutputConfig != null) {
+ mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface();
+ }
+ if (burstCaptureOutputConfig != null) {
+ mClientCaptureSurface = burstCaptureOutputConfig.getSurface();
+ }
+ if (postviewOutputConfig != null) {
+ mClientPostviewSurface = postviewOutputConfig.getSurface();
+ }
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -262,9 +310,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
return;
}
- OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
- OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
- OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+ OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig);
+ OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig);
+ OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig);
mSessionProcessor = mAdvancedExtender.getSessionProcessor();
CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
@@ -300,6 +348,23 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
+ if (Flags.extension10Bit()) {
+ boolean validDynamicRangeProfile = false;
+ for (long profile = DynamicRangeProfiles.STANDARD;
+ profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+ if (output.dynamicRangeProfile == profile) {
+ validDynamicRangeProfile = true;
+ break;
+ }
+ }
+ if (validDynamicRangeProfile) {
+ cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ } else {
+ Log.e(TAG, "Extension configured dynamic range profile "
+ + output.dynamicRangeProfile
+ + " is not valid, using default DynamicRangeProfile.STANDARD");
+ }
+ }
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
@@ -314,7 +379,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
-
+ if (Flags.extension10Bit()) {
+ if (sessionConfig.colorSpace >= 0
+ && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+ sessionConfiguration.setColorSpace(
+ ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ } else {
+ Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+ + " is not valid, using default unspecified color space");
+ }
+ }
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
@@ -362,21 +436,38 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
return ret;
}
- private static OutputSurface initializeParcelable(Surface s) {
+ private static OutputSurface initializeParcelable(OutputConfiguration o) {
OutputSurface ret = new OutputSurface();
- if (s != null) {
+
+ if (o != null && o.getSurface() != null) {
+ Surface s = o.getSurface();
ret.surface = s;
ret.size = new android.hardware.camera2.extension.Size();
Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
ret.size.width = surfaceSize.getWidth();
ret.size.height = surfaceSize.getHeight();
ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
+
+ if (Flags.extension10Bit()) {
+ ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+ ColorSpace colorSpace = o.getColorSpace();
+ if (colorSpace != null) {
+ ret.colorSpace = colorSpace.getId();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
} else {
ret.surface = null;
ret.size = new android.hardware.camera2.extension.Size();
ret.size.width = -1;
ret.size.height = -1;
ret.imageFormat = ImageFormat.UNKNOWN;
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 725b4139bb95..5b32f33777fa 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -58,12 +58,15 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -183,7 +186,14 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -207,7 +217,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index a8066aa74f95..f0c6e2e4e123 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -29,10 +29,13 @@ import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageWriter;
import android.os.Handler;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -130,9 +133,16 @@ public final class CameraExtensionUtils {
public static Surface getBurstCaptureSurface(
@NonNull List<OutputConfiguration> outputConfigs,
@NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
for (OutputConfiguration config : outputConfigs) {
SurfaceInfo surfaceInfo = querySurface(config.getSurface());
- for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
if (surfaceInfo.mFormat == supportedFormat) {
Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 905d911248ca..76888f338615 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -543,11 +543,13 @@ public final class DeviceState {
int identifier = source.readInt();
String name = source.readString8();
ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
- for (int i = 0; i < source.readInt(); i++) {
+ int systemPropertySize = source.readInt();
+ for (int i = 0; i < systemPropertySize; i++) {
systemProperties.add(source.readInt());
}
ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
- for (int j = 0; j < source.readInt(); j++) {
+ int physicalPropertySize = source.readInt();
+ for (int j = 0; j < physicalPropertySize; j++) {
physicalProperties.add(source.readInt());
}
return new DeviceState.Configuration(identifier, name, systemProperties,
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 48aa1bd8e33e..905caf02fc46 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -164,16 +164,18 @@ public final class NanoAppMessage implements Parcelable {
/**
* Sets the isReliable field of the message
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
public void setIsReliable(boolean isReliable) {
mIsReliable = isReliable;
}
/**
* Sets the message sequence number of the message
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
public void setMessageSequenceNumber(int messageSequenceNumber) {
mMessageSequenceNumber = messageSequenceNumber;
}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e6fd16..c5167dbc7d4c 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@ public final class ProgramList implements AutoCloseable {
*
* @param id primary identifier of a program to fetch
* @return the program info, or null if there is no such program on the list
+ *
+ * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+ * with the given primary identifier
*/
+ @Deprecated
public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f0ad05..0740374ad8e2 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@ public final class ProgramSelector implements Parcelable {
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
/**
* 1: AM, 2:FM
+ * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
/**
* 32bit primary identifier for SiriusXM Satellite Radio.
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
/**
* 0-999 range
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
/**
* 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901c454..da6c68646820 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@ public class RadioManager {
* analog handover state managed from the HAL implementation side.
*
* <p>Some radio technologies may not support this, i.e. DAB.
+ *
+ * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+ * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+ * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
*/
+ @Deprecated
public static final int CONFIG_FORCE_ANALOG = 2;
/**
* Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 09e6b5de8a3c..c489c5808728 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,7 +38,6 @@ interface IDreamManager {
boolean isDreaming();
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
- @UnsupportedAppUsage
boolean canStartDreaming(boolean isScreenOn);
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed7680a..799c7545968e 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -20,7 +20,6 @@ import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
import android.os.ICancellationSignal;
import android.os.PersistableBundle;
@@ -35,12 +34,12 @@ import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
*/
oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal,
in ITokenInfoCallback tokenInfoCallback);
- void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index fce3689bb8b3..27e862868858 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -32,7 +32,9 @@ 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.InferenceParams;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -48,14 +50,8 @@ import android.util.Slog;
import com.android.internal.infra.AndroidFuture;
-import androidx.annotation.IntDef;
-
import java.io.File;
import java.io.FileNotFoundException;
-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.Map;
import java.util.Objects;
@@ -232,9 +228,9 @@ public abstract class OnDeviceIntelligenceService extends Service {
* @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 Bundle processingState,
+ public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+ @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
Objects.requireNonNull(callbackExecutor);
if (mRemoteProcessingService == null) {
throw new IllegalStateException("Remote processing service is unavailable.");
@@ -254,7 +250,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
public void onFailure(int errorCode, String errorMessage) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
- new OnDeviceUpdateProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage))));
}
});
@@ -265,7 +261,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
}
private OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ OnDeviceIntelligenceException> wrapFeatureCallback(
IFeatureCallback featureCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -279,7 +275,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -291,7 +287,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
}
private OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
IListFeaturesCallback listFeaturesCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -305,7 +301,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -317,7 +313,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
}
private OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
IFeatureDetailsCallback featureStatusCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -331,7 +327,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureStatusCallback.onFailure(exception.getErrorCode(),
exception.getMessage(), exception.getErrorParams());
@@ -444,7 +440,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
*/
public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+ OnDeviceIntelligenceException> featureDetailsCallback);
/**
@@ -455,7 +451,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
*/
public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+ OnDeviceIntelligenceException> featureCallback);
/**
* List all features which are available in the remote implementation. The implementation might
@@ -465,7 +461,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
* @param listFeaturesCallback callback to populate the features list.
*/
public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+ OnDeviceIntelligenceException> listFeaturesCallback);
/**
* Provides a long value representing the version of the remote implementation processing
@@ -474,60 +470,4 @@ public abstract class OnDeviceIntelligenceService extends Service {
* @param versionConsumer consumer to populate the version.
*/
public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
-
-
- /**
- * Exception type to be populated when calls to {@link #updateProcessingState} fail.
- */
- public static class OnDeviceUpdateProcessingException extends
- OnDeviceIntelligenceServiceException {
- /**
- * The connection to remote service failed and the processing state could not be updated.
- */
- public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
-
-
- /**
- * @hide
- */
- @IntDef(value = {
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
- }, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
- ElementType.FIELD})
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
- super(errorCode);
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
- @NonNull String errorMessage) {
- super(errorCode, errorMessage);
- }
- }
-
- /**
- * Exception type to be used for surfacing errors to service implementation.
- */
- public abstract static class OnDeviceIntelligenceServiceException extends Exception {
- private final int mErrorCode;
-
- public OnDeviceIntelligenceServiceException(int errorCode) {
- this.mErrorCode = errorCode;
- }
-
- public OnDeviceIntelligenceServiceException(int errorCode,
- @NonNull String errorMessage) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- }
}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 7f7f9c28c60e..d943c80e525b 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@ import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
-import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.os.Bundle;
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.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
-import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.ProcessingCallback;
+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;
@@ -51,7 +52,6 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
import android.util.Log;
import android.util.Slog;
@@ -121,7 +121,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void requestTokenInfo(int callerUid, Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
ICancellationSignal cancellationSignal,
ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
@@ -134,7 +134,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void processRequestStreaming(int callerUid, Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
@@ -151,7 +151,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void processRequest(int callerUid, Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
@@ -198,18 +198,17 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@NonNull
public abstract void onTokenInfoRequest(
int callerUid, @NonNull Feature feature,
- @NonNull Content request,
+ @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @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 StreamedProcessingOutcomeReceiver#onNewContent} to continuously
- * provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
+ * 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.
@@ -225,11 +224,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@NonNull
public abstract void onProcessRequestStreaming(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamedProcessingOutcomeReceiver callback);
+ @NonNull StreamingProcessingCallback callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -251,11 +250,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@NonNull
public abstract void onProcessRequest(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull ProcessingOutcomeReceiver callback);
+ @NonNull ProcessingCallback callback);
/**
@@ -267,9 +266,9 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
* @param callback callback to populate the update status and if there are params
* associated with the status.
*/
- public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+ public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull OutcomeReceiver<PersistableBundle,
- OnDeviceUpdateProcessingException> callback);
+ OnDeviceIntelligenceException> callback);
/**
@@ -344,7 +343,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
/**
* Returns the {@link Executor} to use for incoming IPC from request sender into your service
* implementation. For e.g. see
- * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
* Consumer)} where we use the executor to populate the consumer.
* <p>
* Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
@@ -380,13 +379,13 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
});
}
- private ProcessingOutcomeReceiver wrapResponseCallback(
+ private ProcessingCallback wrapResponseCallback(
IResponseCallback callback) {
- return new ProcessingOutcomeReceiver() {
+ return new ProcessingCallback() {
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -394,7 +393,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -404,8 +403,8 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -416,22 +415,22 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
};
}
- private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
+ private StreamingProcessingCallback wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamedProcessingOutcomeReceiver() {
+ return new StreamingProcessingCallback() {
@Override
- public void onNewContent(@androidx.annotation.NonNull Content content) {
+ public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
try {
- callback.onNewContent(content);
+ callback.onNewContent(partialResult);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
}
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -439,7 +438,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -449,8 +448,8 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -462,13 +461,13 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
getCallbackExecutor().execute(() -> contentCallback.accept(
result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
- Content.class)));
+ Bundle.class)));
} else {
getCallbackExecutor().execute(
() -> contentCallback.accept(null));
@@ -476,8 +475,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
});
}
- private OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -491,7 +489,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -503,7 +501,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@NonNull
- private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
IProcessingUpdateStatusCallback callback) {
return new OutcomeReceiver<>() {
@Override
@@ -518,7 +516,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onError(
- @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+ @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
try {
callback.onFailure(error.getErrorCode(), error.getMessage());
} catch (RemoteException e) {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index a08264e625df..ccc17ecccbf9 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -819,19 +819,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
/**
* Called when the keyphrase is spoken.
*
- * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should
- * start a recognition again once they are done handling this detection.
+ * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops
+ * listening for the keyphrase once it's detected. Clients should start a recognition again
+ * once they are done handling this detection.
*
* @param eventPayload Payload data for the detection event. This may contain the trigger
* audio, if requested when calling {@link
- * AlwaysOnHotwordDetector#startRecognition(int)}.
+ * AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link
+ * android.service.wearable.WearableSensingService}.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release:
- // 1. Prepend to the first paragraph:
- // If {@code eventPayload.isRecognitionStopped()} returns true, this...
- // 2. Append to the description for @param eventPayload:
- // ...or if the audio comes from {@link
- // android.service.wearable.WearableSensingService}.
public abstract void onDetected(@NonNull EventPayload eventPayload);
/**
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 60e9de72f154..937aecc8d718 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -363,9 +363,11 @@ public abstract class HotwordDetectionService extends Service
* {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat,
* PersistableBundle)} run} hotword recognition on audio coming from an external connected
* microphone.
- * <p>
- * Upon invoking the {@code callback}, the system closes {@code audioStream} and sends the
- * detection result to the {@link HotwordDetector.Callback hotword detector}.
+ *
+ * <p>Upon invoking the {@code callback}, the system will send the detection result to
+ * the {@link HotwordDetector}'s callback. If {@code
+ * options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
+ * the system will also close the {@code audioStream} after {@code callback} is invoked.
*
* @param audioStream Stream containing audio bytes returned from a microphone
* @param audioFormat Format of the supplied audio
@@ -375,11 +377,6 @@ public abstract class HotwordDetectionService extends Service
* PersistableBundle)}.
* @param callback The callback to use for responding to the detection request.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to:
- // <p>Upon invoking the {@code callback}, the system will send the detection result to
- // the {@link HotwordDetector}'s callback. If {@code
- // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
- // the system will also close the {@code audioStream} after {@code callback} is invoked.
public void onDetect(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa167498..e95b2166e71d 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -28,6 +28,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
+import android.os.Trace;
import android.text.style.ParagraphStyle;
/**
@@ -567,49 +568,59 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
- final int textLength = text.length();
- if (hasAnyInterestingChars(text, textLength)) {
- return null; // There are some interesting characters. Not boring.
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("BoringLayout#isBoring");
+ Trace.setCounter("BoringLayout#textLength", text.length());
}
- if (textDir != null && textDir.isRtl(text, 0, textLength)) {
- return null; // The heuristic considers the whole text RTL. Not boring.
- }
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
- if (styles.length > 0) {
- return null; // There are some ParagraphStyle spans. Not boring.
+ try {
+ final int textLength = text.length();
+ if (hasAnyInterestingChars(text, textLength)) {
+ return null; // There are some interesting characters. Not boring.
+ }
+ if (textDir != null && textDir.isRtl(text, 0, textLength)) {
+ return null; // The heuristic considers the whole text RTL. Not boring.
+ }
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
+ if (styles.length > 0) {
+ return null; // There are some ParagraphStyle spans. Not boring.
+ }
}
- }
- Metrics fm = metrics;
- if (fm == null) {
- fm = new Metrics();
- } else {
- fm.reset();
- }
+ Metrics fm = metrics;
+ if (fm == null) {
+ fm = new Metrics();
+ } else {
+ fm.reset();
+ }
- if (ClientFlags.fixLineHeightForLocale()) {
- if (minimumFontMetrics != null) {
- fm.set(minimumFontMetrics);
- // Because the font metrics is provided by public APIs, adjust the top/bottom with
- // ascent/descent: top must be smaller than ascent, bottom must be larger than
- // descent.
- fm.top = Math.min(fm.top, fm.ascent);
- fm.bottom = Math.max(fm.bottom, fm.descent);
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics != null) {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom
+ // with ascent/descent: top must be smaller than ascent, bottom must be larger
+ // than descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
}
- }
- TextLine line = TextLine.obtain();
- line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
- Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
- 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
- 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
- useFallbackLineSpacing);
- fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
- TextLine.recycle(line);
+ TextLine line = TextLine.obtain();
+ line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
+ 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
+ 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+ useFallbackLineSpacing);
+ fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
+ TextLine.recycle(line);
- return fm;
+ return fm;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
@Override
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7bf7b1f..99ce0ef9fdf1 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -31,6 +31,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
+import android.os.Trace;
import android.text.method.OffsetMapping;
import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
@@ -636,207 +637,224 @@ public class DynamicLayout extends Layout {
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void reflow(CharSequence s, int where, int before, int after) {
- if (s != mBase)
- return;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("DynamicLayout#reflow");
+ }
+ try {
+ if (s != mBase) {
+ return;
+ }
- CharSequence text = mDisplay;
- int len = text.length();
+ CharSequence text = mDisplay;
+ int len = text.length();
- // seek back to the start of the paragraph
+ // seek back to the start of the paragraph
- int find = TextUtils.lastIndexOf(text, '\n', where - 1);
- if (find < 0)
- find = 0;
- else
- find = find + 1;
+ int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+ if (find < 0) {
+ find = 0;
+ } else {
+ find = find + 1;
+ }
- {
- int diff = where - find;
- before += diff;
- after += diff;
- where -= diff;
- }
+ {
+ int diff = where - find;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
- // seek forward to the end of the paragraph
+ // seek forward to the end of the paragraph
- int look = TextUtils.indexOf(text, '\n', where + after);
- if (look < 0)
- look = len;
- else
- look++; // we want the index after the \n
+ int look = TextUtils.indexOf(text, '\n', where + after);
+ if (look < 0) {
+ look = len;
+ } else {
+ look++; // we want the index after the \n
+ }
- int change = look - (where + after);
- before += change;
- after += change;
+ int change = look - (where + after);
+ before += change;
+ after += change;
- // seek further out to cover anything that is forced to wrap together
+ // seek further out to cover anything that is forced to wrap together
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- boolean again;
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ boolean again;
- do {
- again = false;
+ do {
+ again = false;
- Object[] force = sp.getSpans(where, where + after,
- WrapTogetherSpan.class);
+ Object[] force = sp.getSpans(where, where + after,
+ WrapTogetherSpan.class);
- for (int i = 0; i < force.length; i++) {
- int st = sp.getSpanStart(force[i]);
- int en = sp.getSpanEnd(force[i]);
+ for (int i = 0; i < force.length; i++) {
+ int st = sp.getSpanStart(force[i]);
+ int en = sp.getSpanEnd(force[i]);
- if (st < where) {
- again = true;
+ if (st < where) {
+ again = true;
- int diff = where - st;
- before += diff;
- after += diff;
- where -= diff;
- }
+ int diff = where - st;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
- if (en > where + after) {
- again = true;
+ if (en > where + after) {
+ again = true;
- int diff = en - (where + after);
- before += diff;
- after += diff;
+ int diff = en - (where + after);
+ before += diff;
+ after += diff;
+ }
}
- }
- } while (again);
- }
+ } while (again);
+ }
- // find affected region of old layout
+ // find affected region of old layout
- int startline = getLineForOffset(where);
- int startv = getLineTop(startline);
+ int startline = getLineForOffset(where);
+ int startv = getLineTop(startline);
- int endline = getLineForOffset(where + before);
- if (where + after == len)
- endline = getLineCount();
- int endv = getLineTop(endline);
- boolean islast = (endline == getLineCount());
+ int endline = getLineForOffset(where + before);
+ if (where + after == len) {
+ endline = getLineCount();
+ }
+ int endv = getLineTop(endline);
+ boolean islast = (endline == getLineCount());
- // generate new layout for affected text
+ // generate new layout for affected text
- StaticLayout reflowed;
- StaticLayout.Builder b;
+ StaticLayout reflowed;
+ StaticLayout.Builder b;
- synchronized (sLock) {
- reflowed = sStaticLayout;
- b = sBuilder;
- sStaticLayout = null;
- sBuilder = null;
- }
+ synchronized (sLock) {
+ reflowed = sStaticLayout;
+ b = sBuilder;
+ sStaticLayout = null;
+ sBuilder = null;
+ }
- if (b == null) {
- b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
- }
+ if (b == null) {
+ b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
+ }
- b.setText(text, where, where + after)
- .setPaint(getPaint())
- .setWidth(getWidth())
- .setTextDirection(getTextDirectionHeuristic())
- .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
- .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
- .setEllipsizedWidth(mEllipsizedWidth)
- .setEllipsize(mEllipsizeAt)
- .setBreakStrategy(mBreakStrategy)
- .setHyphenationFrequency(mHyphenationFrequency)
- .setJustificationMode(mJustificationMode)
- .setLineBreakConfig(mLineBreakConfig)
- .setAddLastLineLineSpacing(!islast)
- .setIncludePad(false)
- .setUseBoundsForWidth(mUseBoundsForWidth)
- .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
- .setMinimumFontMetrics(mMinimumFontMetrics)
- .setCalculateBounds(true);
-
- reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
- int n = reflowed.getLineCount();
- // If the new layout has a blank line at the end, but it is not
- // the very end of the buffer, then we already have a line that
- // starts there, so disregard the blank line.
-
- if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
- n--;
-
- // remove affected lines from old layout
- mInts.deleteAt(startline, endline - startline);
- mObjects.deleteAt(startline, endline - startline);
-
- // adjust offsets in layout for new height and offsets
-
- int ht = reflowed.getLineTop(n);
- int toppad = 0, botpad = 0;
-
- if (mIncludePad && startline == 0) {
- toppad = reflowed.getTopPadding();
- mTopPadding = toppad;
- ht -= toppad;
- }
- if (mIncludePad && islast) {
- botpad = reflowed.getBottomPadding();
- mBottomPadding = botpad;
- ht += botpad;
- }
+ b.setText(text, where, where + after)
+ .setPaint(getPaint())
+ .setWidth(getWidth())
+ .setTextDirection(getTextDirectionHeuristic())
+ .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
+ .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
+ .setEllipsizedWidth(mEllipsizedWidth)
+ .setEllipsize(mEllipsizeAt)
+ .setBreakStrategy(mBreakStrategy)
+ .setHyphenationFrequency(mHyphenationFrequency)
+ .setJustificationMode(mJustificationMode)
+ .setLineBreakConfig(mLineBreakConfig)
+ .setAddLastLineLineSpacing(!islast)
+ .setIncludePad(false)
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
+ .setCalculateBounds(true);
+
+ reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */,
+ reflowed);
+ int n = reflowed.getLineCount();
+ // If the new layout has a blank line at the end, but it is not
+ // the very end of the buffer, then we already have a line that
+ // starts there, so disregard the blank line.
+
+ if (where + after != len && reflowed.getLineStart(n - 1) == where + after) {
+ n--;
+ }
- mInts.adjustValuesBelow(startline, START, after - before);
- mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+ // remove affected lines from old layout
+ mInts.deleteAt(startline, endline - startline);
+ mObjects.deleteAt(startline, endline - startline);
- // insert new layout
+ // adjust offsets in layout for new height and offsets
- int[] ints;
+ int ht = reflowed.getLineTop(n);
+ int toppad = 0, botpad = 0;
- if (mEllipsize) {
- ints = new int[COLUMNS_ELLIPSIZE];
- ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
- } else {
- ints = new int[COLUMNS_NORMAL];
- }
+ if (mIncludePad && startline == 0) {
+ toppad = reflowed.getTopPadding();
+ mTopPadding = toppad;
+ ht -= toppad;
+ }
+ if (mIncludePad && islast) {
+ botpad = reflowed.getBottomPadding();
+ mBottomPadding = botpad;
+ ht += botpad;
+ }
- Directions[] objects = new Directions[1];
+ mInts.adjustValuesBelow(startline, START, after - before);
+ mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
- for (int i = 0; i < n; i++) {
- final int start = reflowed.getLineStart(i);
- ints[START] = start;
- ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
- ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
+ // insert new layout
- int top = reflowed.getLineTop(i) + startv;
- if (i > 0)
- top -= toppad;
- ints[TOP] = top;
+ int[] ints;
- int desc = reflowed.getLineDescent(i);
- if (i == n - 1)
- desc += botpad;
+ if (mEllipsize) {
+ ints = new int[COLUMNS_ELLIPSIZE];
+ ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+ } else {
+ ints = new int[COLUMNS_NORMAL];
+ }
- ints[DESCENT] = desc;
- ints[EXTRA] = reflowed.getLineExtra(i);
- objects[0] = reflowed.getLineDirections(i);
+ Directions[] objects = new Directions[1];
- final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
- ints[HYPHEN] = StaticLayout.packHyphenEdit(
- reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
- ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
- contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
- MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+ for (int i = 0; i < n; i++) {
+ final int start = reflowed.getLineStart(i);
+ ints[START] = start;
+ ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+ ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
- if (mEllipsize) {
- ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
- ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
- }
+ int top = reflowed.getLineTop(i) + startv;
+ if (i > 0) {
+ top -= toppad;
+ }
+ ints[TOP] = top;
- mInts.insertAt(startline + i, ints);
- mObjects.insertAt(startline + i, objects);
- }
+ int desc = reflowed.getLineDescent(i);
+ if (i == n - 1) {
+ desc += botpad;
+ }
- updateBlocks(startline, endline - 1, n);
+ ints[DESCENT] = desc;
+ ints[EXTRA] = reflowed.getLineExtra(i);
+ objects[0] = reflowed.getLineDirections(i);
+
+ final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+ ints[HYPHEN] = StaticLayout.packHyphenEdit(
+ reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
+ ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+ contentMayProtrudeFromLineTopOrBottom(text, start, end)
+ ? MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+
+ if (mEllipsize) {
+ ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+ ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+ }
- b.finish();
- synchronized (sLock) {
- sStaticLayout = reflowed;
- sBuilder = b;
+ mInts.insertAt(startline + i, ints);
+ mObjects.insertAt(startline + i, objects);
+ }
+
+ updateBlocks(startline, endline - 1, n);
+
+ b.finish();
+ synchronized (sLock) {
+ sStaticLayout = reflowed;
+ sBuilder = b;
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b19c6d3..ce238a77ad87 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,7 @@ import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
+import android.os.Trace;
import android.text.method.TextKeyListener;
import android.text.style.AlignmentSpan;
import android.text.style.LeadingMarginSpan;
@@ -70,6 +71,11 @@ import java.util.Locale;
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ /** @hide */
+ protected static final boolean TRACE_LAYOUT = Build.isDebuggable();
+
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -472,40 +478,51 @@ public abstract class Layout {
@Nullable Path selectionPath,
@Nullable Paint selectionPaint,
int cursorOffsetVertical) {
- float leftShift = 0;
- if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
- RectF drawingRect = computeDrawingBoundingBox();
- if (drawingRect.left < 0) {
- leftShift = -drawingRect.left;
- canvas.translate(leftShift, 0);
- }
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("Layout#draw");
}
- final long lineRange = getLineRangeForDraw(canvas);
- int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
- int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
- if (lastLine < 0) return;
+ try {
+ float leftShift = 0;
+ if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
+ RectF drawingRect = computeDrawingBoundingBox();
+ if (drawingRect.left < 0) {
+ leftShift = -drawingRect.left;
+ canvas.translate(leftShift, 0);
+ }
+ }
+ final long lineRange = getLineRangeForDraw(canvas);
+ int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+ int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+ if (lastLine < 0) return;
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawBackground(canvas, firstLine, lastLine);
- } else {
- drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawBackground(canvas, firstLine, lastLine);
+ } else {
+ drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- drawText(canvas, firstLine, lastLine);
+ drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ // Since high contrast text draws a solid rectangle background behind the text, it
+ // covers up the highlights and selections. In this case we draw over the top of the
+ // text with a blend mode that ensures the text stays high-contrast.
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- if (leftShift != 0) {
- // Manually translate back to the original position because of b/324498002, using
- // save/restore disappears the toggle switch drawables.
- canvas.translate(-leftShift, 0);
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ canvas.translate(-leftShift, 0);
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd068c9..14401a6b231b 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -25,6 +25,8 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
+import android.os.Build;
+import android.os.Trace;
import android.text.style.MetricAffectingSpan;
import com.android.internal.util.Preconditions;
@@ -78,6 +80,8 @@ import java.util.Objects;
public class PrecomputedText implements Spannable {
private static final char LINE_FEED = '\n';
+ private static final boolean TRACE_PCT = Build.isDebuggable();
+
/**
* The information required for building {@link PrecomputedText}.
*
@@ -447,35 +451,47 @@ public class PrecomputedText implements Spannable {
private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
@NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
- }
- LineBreakConfig config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && pct.getParagraphCount() != 1) {
- // If the text has multiple paragraph, resolve line break word style auto to none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphsFromPrecomputedText");
+ Trace.setCounter("PrecomputedText#textCharCount", pct.length());
}
- ArrayList<ParagraphInfo> result = new ArrayList<>();
- for (int i = 0; i < pct.getParagraphCount(); ++i) {
- final int paraStart = pct.getParagraphStart(i);
- final int paraEnd = pct.getParagraphEnd(i);
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, pct, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, true,
- pct.getMeasuredParagraph(i), null /* no recycle */)));
+ try {
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+ } else {
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ }
+ LineBreakConfig config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && pct.getParagraphCount() != 1) {
+ // If the text has multiple paragraph, resolve line break word style auto to none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
+ for (int i = 0; i < pct.getParagraphCount(); ++i) {
+ final int paraStart = pct.getParagraphStart(i);
+ final int paraEnd = pct.getParagraphEnd(i);
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, pct, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, true,
+ pct.getMeasuredParagraph(i), null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- return result.toArray(new ParagraphInfo[result.size()]);
}
/** @hide */
@@ -483,53 +499,65 @@ public class PrecomputedText implements Spannable {
@NonNull CharSequence text, @NonNull Params params,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout,
boolean computeBounds) {
- ArrayList<ParagraphInfo> result = new ArrayList<>();
-
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(params);
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphs");
+ Trace.setCounter("PrecomputedText#textCharCount", text.length());
}
+ try {
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
- LineBreakConfig config = null;
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
- // end.
- paraEnd = end;
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(params);
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
} else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
}
- if (config == null) {
- config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && !(paraStart == start && paraEnd == end)) {
- // If the text has multiple paragraph, resolve line break word style auto to
- // none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ LineBreakConfig config = null;
+ int paraEnd = 0;
+ for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+ // end.
+ paraEnd = end;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ }
+
+ if (config == null) {
+ config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && !(paraStart == start && paraEnd == end)) {
+ // If the text has multiple paragraph, resolve line break word style auto to
+ // none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
}
- }
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, text, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
- null /* no hint */,
- null /* no recycle */)));
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, text, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
+ null /* no hint */,
+ null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- return result.toArray(new ParagraphInfo[result.size()]);
}
// Use PrecomputedText.create instead.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9ea8baf..d1b14d14c7d8 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -542,10 +542,20 @@ public class StaticLayout extends Layout {
*/
@NonNull
public StaticLayout build() {
- StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
- ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
- Builder.recycle(this);
- return result;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#build");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
+ }
+ try {
+ StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
+ ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
+ Builder.recycle(this);
+ return result;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
/**
@@ -562,16 +572,21 @@ public class StaticLayout extends Layout {
*/
/* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
boolean trackpadding, StaticLayout recycle) {
- if (recycle == null) {
- recycle = new StaticLayout();
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#forDynamicLayout");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
}
- Trace.beginSection("Generating StaticLayout For DynamicLayout");
try {
+ if (recycle == null) {
+ recycle = new StaticLayout();
+ }
recycle.generate(this, mIncludePad, trackpadding);
+ return recycle;
} finally {
- Trace.endSection();
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
- return recycle;
}
private CharSequence mText;
@@ -727,12 +742,7 @@ public class StaticLayout extends Layout {
mLeftIndents = b.mLeftIndents;
mRightIndents = b.mRightIndents;
- Trace.beginSection("Constructing StaticLayout");
- try {
- generate(b, b.mIncludePad, trackPadding);
- } finally {
- Trace.endSection();
- }
+ generate(b, b.mIncludePad, trackPadding);
}
private static int getBaseHyphenationFrequency(int frequency) {
@@ -842,14 +852,23 @@ public class StaticLayout extends Layout {
case PrecomputedText.Params.UNUSABLE:
break;
case PrecomputedText.Params.NEED_RECOMPUTE:
- final PrecomputedText.Params newParams =
- new PrecomputedText.Params.Builder(paint)
- .setBreakStrategy(b.mBreakStrategy)
- .setHyphenationFrequency(b.mHyphenationFrequency)
- .setTextDirection(textDir)
- .setLineBreakConfig(b.mLineBreakConfig)
- .build();
- precomputed = PrecomputedText.create(precomputed, newParams);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#recomputePct");
+ }
+ try {
+ final PrecomputedText.Params newParams =
+ new PrecomputedText.Params.Builder(paint)
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
+ .setTextDirection(textDir)
+ .setLineBreakConfig(b.mLineBreakConfig)
+ .build();
+ precomputed = PrecomputedText.create(precomputed, newParams);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
paragraphInfo = precomputed.getParagraphInfo();
break;
case PrecomputedText.Params.USABLE:
@@ -860,232 +879,261 @@ public class StaticLayout extends Layout {
}
if (paragraphInfo == null) {
- final PrecomputedText.Params param = new PrecomputedText.Params(paint,
- b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
- paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
- bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#computePct");
+ }
+ try {
+ final PrecomputedText.Params param = new PrecomputedText.Params(paint,
+ b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
+ paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
+ bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
- final int paraStart = paraIndex == 0
- ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
- final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
-
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
-
- LineHeightSpan[] chooseHt = null;
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
-
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#processParagraph");
+ Trace.setCounter("StaticLayout#paragraph", paraIndex);
+ }
+ try {
+ final int paraStart = paraIndex == 0
+ ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+ final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
+
+ LineHeightSpan[] chooseHt = null;
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
+
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
}
- }
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
- } else {
- if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
- }
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ }
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
- chooseHtv[i] = getLineTop(getLineForOffset(o));
- } else {
- // starts in this paragraph
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
- chooseHtv[i] = v;
+ chooseHtv[i] = v;
+ }
}
}
}
- }
- // tab stop locations
- float[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- float[] stops = new float[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = (float) spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
- }
- }
-
- final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
- final char[] chs = measuredPara.getChars();
- final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
- final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-
- constraints.setWidth(restWidth);
- constraints.setIndent(firstWidth, firstWidthLineCount);
- constraints.setTabStops(variableTabStops, TAB_INCREMENT);
-
- LineBreaker.Result res = lineBreaker.computeLineBreaks(
- measuredPara.getMeasuredText(), constraints, mLineCount);
- int breakCount = res.getLineCount();
- if (lineBreakCapacity < breakCount) {
- lineBreakCapacity = breakCount;
- breaks = new int[lineBreakCapacity];
- lineWidths = new float[lineBreakCapacity];
- ascents = new float[lineBreakCapacity];
- descents = new float[lineBreakCapacity];
- hasTabs = new boolean[lineBreakCapacity];
- hyphenEdits = new int[lineBreakCapacity];
- }
-
- for (int i = 0; i < breakCount; ++i) {
- breaks[i] = res.getLineBreakOffset(i);
- lineWidths[i] = res.getLineWidth(i);
- ascents[i] = res.getLineAscent(i);
- descents[i] = res.getLineDescent(i);
- hasTabs[i] = res.hasLineTab(i);
- hyphenEdits[i] =
- packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
- }
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width
- float width = 0;
- boolean hasTab = false; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
- } else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += measuredPara.getCharWidthAt(j);
+ // tab stop locations
+ float[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ float[] stops = new float[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = (float) spans[i].getTabStop();
}
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
}
- hasTab |= hasTabs[i];
}
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- hasTabs[remainingLineCount - 1] = hasTab;
- breakCount = remainingLineCount;
- }
+ final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+ final char[] chs = measuredPara.getChars();
+ final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+ final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
- // here is the offset of the starting character of the line we are currently
- // measuring
- int here = paraStart;
-
- int fmTop = defaultTop;
- int fmBottom = defaultBottom;
- int fmAscent = defaultAscent;
- int fmDescent = defaultDescent;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
+ constraints.setWidth(restWidth);
+ constraints.setIndent(firstWidth, firstWidthLineCount);
+ constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("LineBreaker#computeLineBreaks");
}
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
+ LineBreaker.Result res;
+ try {
+ res = lineBreaker.computeLineBreaks(
+ measuredPara.getMeasuredText(), constraints, mLineCount);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
+ int breakCount = res.getLineCount();
+ if (lineBreakCapacity < breakCount) {
+ lineBreakCapacity = breakCount;
+ breaks = new int[lineBreakCapacity];
+ lineWidths = new float[lineBreakCapacity];
+ ascents = new float[lineBreakCapacity];
+ descents = new float[lineBreakCapacity];
+ hasTabs = new boolean[lineBreakCapacity];
+ hyphenEdits = new int[lineBreakCapacity];
}
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
+ for (int i = 0; i < breakCount; ++i) {
+ breaks[i] = res.getLineBreakOffset(i);
+ lineWidths[i] = res.getLineWidth(i);
+ ascents[i] = res.getLineAscent(i);
+ descents[i] = res.getLineDescent(i);
+ hasTabs[i] = res.hasLineTab(i);
+ hyphenEdits[i] =
+ packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
}
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width
+ float width = 0;
+ boolean hasTab = false; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
+ } else {
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += measuredPara.getCharWidthAt(j);
+ }
+ }
+ hasTab |= hasTabs[i];
+ }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ hasTabs[remainingLineCount - 1] = hasTab;
- final int ascent = isFallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = isFallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
+ breakCount = remainingLineCount;
+ }
- // The fallback ascent/descent may be larger than top/bottom of the default font
- // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
- // clipping.
- if (isFallbackLineSpacing) {
- if (ascent < fmTop) {
- fmTop = ascent;
- }
- if (descent > fmBottom) {
- fmBottom = descent;
- }
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
+
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
}
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
- measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
- paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
- paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
- fmTop = Math.min(defaultTop, fm.top);
- fmBottom = Math.max(defaultBottom, fm.bottom);
- fmAscent = Math.min(defaultAscent, fm.ascent);
- fmDescent = Math.max(defaultDescent, fm.descent);
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
- here = endPos;
- breakIndex++;
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
+
+ boolean moreChars = (endPos < bufEnd);
+
+ final int ascent = isFallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = isFallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
+
+ // The fallback ascent/descent may be larger than top/bottom of the default
+ // font metrics. Adjust top/bottom with ascent/descent for avoiding
+ // unexpected clipping.
+ if (isFallbackLineSpacing) {
+ if (ascent < fmTop) {
+ fmTop = ascent;
+ }
+ if (descent > fmBottom) {
+ fmBottom = descent;
+ }
+ }
+
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
+ measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
+ paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+ paint, moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = Math.min(defaultTop, fm.top);
+ fmBottom = Math.max(defaultBottom, fm.bottom);
+ fmAscent = Math.min(defaultAscent, fm.ascent);
+ fmDescent = Math.max(defaultDescent, fm.descent);
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
+ }
}
}
- }
- if (paraEnd == bufEnd) {
- break;
+ if (paraEnd == bufEnd) {
+ break;
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
@@ -1179,9 +1227,18 @@ public class StaticLayout extends Layout {
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(start, end, measured, widthStart,
- ellipsisWidth, ellipsize, j,
- textWidth, paint, forceEllipsis);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#calculateEllipsis");
+ }
+ try {
+ calculateEllipsis(start, end, measured, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
} else {
mLines[mColumns * j + ELLIPSIS_START] = 0;
mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bde9c7770eb7..a43947806fa7 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@ import android.graphics.RectF;
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextRunShaper;
import android.os.Build;
+import android.os.Trace;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
@@ -56,6 +57,8 @@ import java.util.ArrayList;
public class TextLine {
private static final boolean DEBUG = false;
+ private static final boolean TRACE_TEXTLINE = Build.isDebuggable();
+
private static final char TAB_CHAR = '\t';
private TextPaint mPaint;
@@ -430,28 +433,37 @@ public class TextLine {
* @param bottom the bottom of the line
*/
void draw(Canvas c, float x, int top, int y, int bottom) {
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
-
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
-
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen, runFlag);
-
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- h = mDir * nextTab(h * mDir);
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#draw");
+ }
+ try {
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+ runIndex != (runCount - 1) || j != mLen, runFlag);
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ h = mDir * nextTab(h * mDir);
+ }
+ segStart = j + 1;
}
- segStart = j + 1;
}
}
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
}
}
@@ -564,63 +576,76 @@ public class TextLine {
*/
public float measure(@IntRange(from = 0) int offset, boolean trailing,
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
- if (offset > mLen) {
- throw new IndexOutOfBoundsException(
- "offset(" + offset + ") should be less than line limit(" + mLen + ")");
- }
- if (lineInfo != null) {
- lineInfo.setClusterCount(0);
- }
- final int target = trailing ? offset - 1 : offset;
- if (target < 0) {
- return 0;
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#measure");
}
+ try {
+ if (offset > mLen) {
+ throw new IndexOutOfBoundsException(
+ "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ }
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(0);
+ }
+ final int target = trailing ? offset - 1 : offset;
+ if (target < 0) {
+ return 0;
+ }
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
-
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- final boolean targetIsInThisSegment = target >= segStart && target < j;
- final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
- if (targetIsInThisSegment && sameDirection) {
- return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h, lineInfo, runFlag);
- }
-
- final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h, lineInfo, runFlag);
- h += sameDirection ? segmentWidth : -segmentWidth;
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final boolean targetIsInThisSegment = target >= segStart && target < j;
+ final boolean sameDirection =
+ (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+
+ if (targetIsInThisSegment && sameDirection) {
+ return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds,
+ null,
+ 0, h, lineInfo, runFlag);
+ }
- if (targetIsInThisSegment) {
- return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h, lineInfo, runFlag);
- }
+ final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi,
+ drawBounds,
+ null, 0, h, lineInfo, runFlag);
+ h += sameDirection ? segmentWidth : -segmentWidth;
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- if (offset == j) {
- return h;
+ if (targetIsInThisSegment) {
+ return h + measureRun(segStart, offset, j, runIsRtl, null, null, null,
+ 0,
+ h, lineInfo, runFlag);
}
- h = mDir * nextTab(h * mDir);
- if (target == j) {
- return h;
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (offset == j) {
+ return h;
+ }
+ h = mDir * nextTab(h * mDir);
+ if (target == j) {
+ return h;
+ }
}
- }
- segStart = j + 1;
+ segStart = j + 1;
+ }
}
}
- }
- return h;
+ return h;
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
+ }
}
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab94952..aff1d4a4ee12 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@ flag {
is_fixed_read_only: true
bug: "324676775"
}
+
+flag {
+ name: "handwriting_cursor_position"
+ namespace: "text"
+ description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
+ bug: "323376217"
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index a6724da02bf2..a4c3ed96f2ce 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
+import android.annotation.NonNull;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
@@ -33,9 +34,12 @@ import android.content.pm.parsing.result.ParseResult;
import android.os.Build;
import android.os.Trace;
import android.os.incremental.V4Signature;
+import android.util.ArrayMap;
import android.util.Pair;
+import android.util.Slog;
import android.util.jar.StrictJarFile;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -63,8 +67,14 @@ import java.util.zip.ZipEntry;
*/
public class ApkSignatureVerifier {
+ private static final String LOG_TAG = "ApkSignatureVerifier";
+
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
+ @GuardedBy("sOverrideSigningDetails")
+ private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails =
+ new ArrayMap<>();
+
/**
* Verifies the provided APK and returns the certificates associated with each signer.
*/
@@ -95,7 +105,54 @@ public class ApkSignatureVerifier {
if (result.isError()) {
return input.error(result);
}
- return input.success(result.getResult().signingDetails);
+ SigningDetails signingDetails = result.getResult().signingDetails;
+ if (Build.isDebuggable()) {
+ SigningDetails overrideSigningDetails;
+ synchronized (sOverrideSigningDetails) {
+ overrideSigningDetails = sOverrideSigningDetails.get(signingDetails);
+ }
+ if (overrideSigningDetails != null) {
+ Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath);
+ signingDetails = overrideSigningDetails;
+ }
+ }
+ return input.success(signingDetails);
+ }
+
+ /**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ */
+ public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails);
+ }
+ }
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ */
+ public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.remove(oldSigningDetails);
+ }
+ }
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ */
+ public static void clearOverrideSigningDetails() {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.clear();
+ }
}
/**
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 66655fca8fc3..29c83509dbf2 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -557,7 +559,8 @@ public class HandwritingInitiator {
}
private void requestFocusWithoutReveal(View view) {
- if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
+ if (!handwritingCursorPosition() && view instanceof EditText editText
+ && !mState.mStylusDownWithinEditorBounds) {
// If the stylus down point was inside the EditText's bounds, then the EditText will
// automatically set its cursor position nearest to the stylus down point when it
// gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@ public class HandwritingInitiator {
} else {
view.requestFocus();
}
+ if (handwritingCursorPosition() && view instanceof EditText editText) {
+ // Move the cursor to the end of the paragraph closest to the stylus down point.
+ view.getLocationInWindow(mTempLocation);
+ int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
+ int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
+ editText.getLayout().getLineStart(line));
+ if (paragraphEnd < 0) {
+ paragraphEnd = editText.getText().length();
+ }
+ editText.setSelection(paragraphEnd);
+ }
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a5ff48fd3ca6..0ac893665241 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2372,6 +2372,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
/**
+ * This indicates that the frame rate category was chosen for an unknown reason.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_UNKNOWN = 0x0000_0000;
+
+ /**
* This indicates that the frame rate category was chosen because it was a small area update.
* @hide
*/
@@ -2402,9 +2408,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+ /**
+ * This indicates that the frame rate category was chosen because the view has a velocity
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_VELOCITY = 0x0600_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is idle.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+
private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
- private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ /**
+ * @hide
+ */
+ protected static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
// Used to set frame rate compatibility.
@Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -33778,6 +33799,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return;
}
}
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ float sizePercentage = getSizePercentage();
+ viewRootImpl.recordViewPercentage(sizePercentage);
+ }
int frameRateCategory;
if (Float.isNaN(mPreferredFrameRate)) {
frameRateCategory = calculateFrameRateCategory(width, height);
@@ -33806,11 +33831,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
- if (sToolkitMetricsForFrameRateDecisionFlagValue) {
- int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
- viewRootImpl.recordCategory(category, reason, this);
- }
- viewRootImpl.votePreferredFrameRateCategory(category);
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
mLastFrameRateCategory = frameRateCategory;
}
}
@@ -33906,9 +33928,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * This function is mainly used for migrating infrequent layer lagic
+ * This function is mainly used for migrating infrequent layer logic
* from SurfaceFlinger to Toolkit.
- * The infrequent layter logic includes:
+ * The infrequent layer logic includes:
* - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
* - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
* - otherwise, use the previous category value.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6f178bb7ea3c..8c3cf5f7fe71 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,11 +32,14 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -847,6 +850,8 @@ public final class ViewRootImpl implements ViewParent,
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
+ // The largest view size percentage to the display size. Used on trace to collect metric.
+ private float mLargestChildPercentage = 0.0f;
// The reason the category was changed.
private int mFrameRateCategoryChangeReason = 0;
private String mFrameRateCategoryView;
@@ -4854,6 +4859,10 @@ public final class ViewRootImpl implements ViewParent,
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
+
+ long percentage = (long) (mLargestChildPercentage * 100);
+ Trace.setCounter(mLargestViewTraceName, percentage);
+ mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -6540,6 +6549,8 @@ public final class ViewRootImpl implements ViewParent,
case MSG_CHECK_INVALIDATION_IDLE:
if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_IDLE;
+ mFrameRateCategoryView = null;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
} else {
@@ -12367,24 +12378,37 @@ public final class ViewRootImpl implements ViewParent,
|| (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
&& mPreferredFrameRate > 0)) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+ // We've received a velocity, so we'll let the velocity control the
+ // frame rate unless we receive additional motion events.
+ mIsTouchBoosting = false;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+ mFrameRateCategoryView = null;
+ } else {
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ }
}
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- String reason = "none";
- switch (mFrameRateCategoryChangeReason) {
- case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
- case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
- case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
- case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
- case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
- }
- String sourceView = mFrameRateCategoryView == null ? "No View Given"
+ String reason = reasonToString(mFrameRateCategoryChangeReason);
+ String sourceView = mFrameRateCategoryView == null ? "-"
: mFrameRateCategoryView;
+ if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
+ reason = "touch boost";
+ sourceView = "-";
+ } else if (categoryFromConflictedFrameRates == frameRateCategory
+ && frameRateCategory != preferredFrameRateCategory
+ && mIsFrameRateConflicted
+ ) {
+ reason = "conflict";
+ sourceView = "-";
+ }
+ String category = categoryToString(frameRateCategory);
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
- + frameRateCategory + ", reason " + reason + ", "
+ + category + ", reason " + reason + ", "
+ sourceView);
}
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
@@ -12398,6 +12422,35 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private static String categoryToString(int frameRateCategory) {
+ String category;
+ switch (frameRateCategory) {
+ case FRAME_RATE_CATEGORY_NO_PREFERENCE -> category = "no preference";
+ case FRAME_RATE_CATEGORY_LOW -> category = "low";
+ case FRAME_RATE_CATEGORY_NORMAL -> category = "normal";
+ case FRAME_RATE_CATEGORY_HIGH_HINT -> category = "high hint";
+ case FRAME_RATE_CATEGORY_HIGH -> category = "high";
+ default -> category = "default";
+ }
+ return category;
+ }
+
+ private static String reasonToString(int reason) {
+ String str;
+ switch (reason) {
+ case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> str = "intermittent";
+ case FRAME_RATE_CATEGORY_REASON_SMALL -> str = "small";
+ case FRAME_RATE_CATEGORY_REASON_LARGE -> str = "large";
+ case FRAME_RATE_CATEGORY_REASON_REQUESTED -> str = "requested";
+ case FRAME_RATE_CATEGORY_REASON_INVALID -> str = "invalid frame rate";
+ case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
+ case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
+ case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+ default -> str = String.valueOf(reason);
+ }
+ return str;
+ }
+
private void setPreferredFrameRate(float preferredFrameRate) {
if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
&& preferredFrameRate > 0)) {
@@ -12417,17 +12470,6 @@ public final class ViewRootImpl implements ViewParent,
mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
- // We've received a velocity, so we'll let the velocity control the
- // frame rate unless we receive additional motion events.
- mIsTouchBoosting = false;
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.instant(
- Trace.TRACE_TAG_VIEW,
- "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
- );
- }
- }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
} finally {
@@ -12468,7 +12510,7 @@ public final class ViewRootImpl implements ViewParent,
* @param frameRateCategory the preferred frame rate category of a View
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRateCategory(int frameRateCategory) {
+ public void votePreferredFrameRateCategory(int frameRateCategory, int reason, View view) {
if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH) {
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
} else if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
@@ -12479,6 +12521,7 @@ public final class ViewRootImpl implements ViewParent,
mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT;
}
+ int oldCategory = mPreferredFrameRateCategory;
if (mFrameRateCategoryHighCount > 0) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
} else if (mFrameRateCategoryHighHintCount > 0) {
@@ -12490,6 +12533,13 @@ public final class ViewRootImpl implements ViewParent,
}
mHasInvalidation = true;
checkIdleness();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
+ && mPreferredFrameRateCategory != oldCategory
+ && mPreferredFrameRateCategory == frameRateCategory
+ ) {
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view.getClass().getSimpleName();
+ }
}
/**
@@ -12617,12 +12667,10 @@ public final class ViewRootImpl implements ViewParent,
mWindowlessBackKeyCallback = callback;
}
- void recordCategory(int category, int reason, View view) {
+ void recordViewPercentage(float percentage) {
if (!Trace.isEnabled()) return;
- if (category > mPreferredFrameRateCategory) {
- mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view.getClass().getSimpleName();
- }
+ // Record the largest view of percentage to the display size.
+ mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
}
/**
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index e215950fe26a..614df7c10456 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -142,7 +142,7 @@ interface IAccessibilityManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1acfc1b3bb0a..644a7a925f81 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -233,6 +233,28 @@ public class AutofillFeatureFlags {
public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
"include_invisible_view_group_in_assist_structure";
+ /**
+ * Bugfix flag, Autofill should ignore views resetting to empty states.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY =
+ "ignore_view_state_reset_to_empty";
+
+ /**
+ * Bugfix flag, Autofill should ignore view updates if an Auth intent is showing.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#relayout
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING =
+ "ignore_relayout_auth_pending";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -494,6 +516,22 @@ public class AutofillFeatureFlags {
false);
}
+ /** @hide */
+ public static boolean shouldIgnoreViewStateResetToEmpty() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
+ false);
+ }
+
+ /** @hide */
+ public static boolean shouldIgnoreRelayoutWhenAuthPending() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING,
+ false);
+ }
+
/**
* Whether should enable multi-line filter
*
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1484bfbb9df9..e15baaeef570 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -974,7 +974,7 @@ public final class AutofillManager {
mShouldIncludeInvisibleViewInAssistStructure =
AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
- mRelayoutFix = Flags.relayout();
+ mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c53489ba3a..d12eda35c745 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@ public abstract class WebSettings {
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@ public abstract class WebSettings {
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc38706e..d40eedaea77b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@ public class Editor {
}
}
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ // If high contrast text is drawing background rectangles behind the text, those cover up
+ // the cursor and correction highlighter etc. So just draw the text first, then draw the
+ // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+ if (shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+
if (mCorrectionHighlighter != null) {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
@@ -2136,9 +2147,19 @@ public class Editor {
mInsertModeController.onDraw(canvas);
}
+ if (!shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+ }
+
+ private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+ List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+ int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
- selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+ selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+ shouldDrawHighlightsOnTop);
} else {
layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@ public class Editor {
private void drawHardwareAccelerated(Canvas canvas, Layout layout,
List<Path> highlightPaths, List<Paint> highlightPaints,
- Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+ Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+ boolean shouldDrawHighlightsOnTop) {
final long lineRange = layout.getLineRangeForDraw(canvas);
int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
- && canvas.isHighContrastTextEnabled();
if (!shouldDrawHighlightsOnTop) {
layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80096c5..e3caf709cfe8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@ public class RemoteViews implements Parcelable, Filter {
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated use
+ * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
*/
+ @Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
if (remoteAdapterConversion()) {
addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 172146215257..0373539c44ea 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return x;
}
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- int getLineAtCoordinate(float y) {
+ public int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
y = Math.max(0.0f, y);
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index e572853e5d5d..c62eee40da98 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -18,7 +18,6 @@ package android.window;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -30,6 +29,8 @@ import android.view.SurfaceControlViewHost;
import com.android.window.flags.Flags;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.Objects;
/**
@@ -51,28 +52,51 @@ import java.util.Objects;
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
public final class InputTransferToken implements Parcelable {
+ private static native long nativeCreate();
+ private static native long nativeCreate(IBinder token);
+ private static native void nativeWriteToParcel(long nativeObject, Parcel out);
+ private static native long nativeReadFromParcel(Parcel in);
+ private static native IBinder nativeGetBinderToken(long nativeObject);
+ private static native long nativeGetNativeInputTransferTokenFinalizer();
+ private static native boolean nativeEquals(long nativeObject1, long nativeObject2);
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(InputTransferToken.class.getClassLoader(),
+ nativeGetNativeInputTransferTokenFinalizer());
+
/**
* @hide
*/
- @NonNull
- public final IBinder mToken;
+ public final long mNativeObject;
+
+ private InputTransferToken(long nativeObject) {
+ mNativeObject = nativeObject;
+ sRegistry.registerNativeAllocation(this, nativeObject);
+ }
/**
* @hide
*/
public InputTransferToken(@NonNull IBinder token) {
- mToken = token;
+ this(nativeCreate(token));
}
/**
* @hide
*/
public InputTransferToken() {
- mToken = new Binder();
+ this(nativeCreate());
+ }
+
+ /**
+ * @hide
+ */
+ public IBinder getToken() {
+ return nativeGetBinderToken(mNativeObject);
}
private InputTransferToken(Parcel in) {
- mToken = in.readStrongBinder();
+ this(nativeReadFromParcel(in));
}
/**
@@ -88,7 +112,7 @@ public final class InputTransferToken implements Parcelable {
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
+ nativeWriteToParcel(mNativeObject, dest);
}
public static final @NonNull Creator<InputTransferToken> CREATOR = new Creator<>() {
@@ -101,13 +125,12 @@ public final class InputTransferToken implements Parcelable {
}
};
-
/**
* @hide
*/
@Override
public int hashCode() {
- return Objects.hash(mToken);
+ return Objects.hash(getToken());
}
/**
@@ -118,7 +141,8 @@ public final class InputTransferToken implements Parcelable {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
InputTransferToken other = (InputTransferToken) obj;
- return other.mToken == mToken;
+ if (other.mNativeObject == mNativeObject) return true;
+ return nativeEquals(mNativeObject, other.mNativeObject);
}
}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 6999e5bdc527..50f519122802 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -59,28 +59,35 @@ public final class StartingWindowRemovalInfo implements Parcelable {
*/
public boolean playRevealAnimation;
- /** The mode is no need to defer removing the starting window for IME */
- public static final int DEFER_MODE_NONE = 0;
+ /** The mode is default defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_DEFAULT = 0;
- /** The mode to defer removing the starting window until IME has drawn */
+ /** The mode to defer removing the snapshot starting window until IME has drawn. */
public static final int DEFER_MODE_NORMAL = 1;
- /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+ /**
+ * The mode to defer the snapshot starting window removal until IME drawn and finished the
+ * rotation.
+ */
public static final int DEFER_MODE_ROTATION = 2;
+ /** The mode is no need to defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_NONE = 3;
+
@IntDef(prefix = { "DEFER_MODE_" }, value = {
- DEFER_MODE_NONE,
+ DEFER_MODE_DEFAULT,
DEFER_MODE_NORMAL,
DEFER_MODE_ROTATION,
+ DEFER_MODE_NONE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeferMode {}
/**
- * Whether need to defer removing the starting window for IME.
+ * Whether need to defer removing the snapshot starting window.
* @hide
*/
- public @DeferMode int deferRemoveForImeMode;
+ public @DeferMode int deferRemoveMode;
/**
* The rounded corner radius
@@ -116,7 +123,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
- deferRemoveForImeMode = source.readInt();
+ deferRemoveMode = source.readInt();
roundedCornerRadius = source.readFloat();
windowlessSurface = source.readBoolean();
removeImmediately = source.readBoolean();
@@ -128,7 +135,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
dest.writeTypedObject(windowAnimationLeash, flags);
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
- dest.writeInt(deferRemoveForImeMode);
+ dest.writeInt(deferRemoveMode);
dest.writeFloat(roundedCornerRadius);
dest.writeBoolean(windowlessSurface);
dest.writeBoolean(removeImmediately);
@@ -140,7 +147,7 @@ public final class StartingWindowRemovalInfo implements Parcelable {
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForImeMode=" + deferRemoveForImeMode
+ + " deferRemoveMode=" + deferRemoveMode
+ " windowlessSurface=" + windowlessSurface
+ " removeImmediately=" + removeImmediately + "}";
}
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff8e20b..7e77f150b63b 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@ public final class TaskFragmentOperation implements Parcelable {
/**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
- * event callback.
+ * event callback. The decor surface can be used to draw the divider between TaskFragments or
+ * other decorations.
*/
public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
@@ -135,6 +137,15 @@ public final class TaskFragmentOperation implements Parcelable {
*/
public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ /**
+ * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+ * below any TaskFragments in untrusted mode or any activities with uid different from the
+ * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+ * surface is placed above all the non-boosted windows in the Task, the content of these
+ * non-boosted windows will be hidden and inputs are disabled.
+ */
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@ public final class TaskFragmentOperation implements Parcelable {
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -186,12 +198,18 @@ public final class TaskFragmentOperation implements Parcelable {
private final boolean mMoveToBottomIfClearWhenLaunch;
+ private final boolean mBooleanValue;
+
+ @Nullable
+ private final SurfaceControl.Transaction mSurfaceTransaction;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
@Nullable TaskFragmentAnimationParams animationParams,
- boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+ boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+ boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -202,6 +220,8 @@ public final class TaskFragmentOperation implements Parcelable {
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ mBooleanValue = booleanValue;
+ mSurfaceTransaction = surfaceTransaction;
}
private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@ public final class TaskFragmentOperation implements Parcelable {
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+ mBooleanValue = in.readBoolean();
+ mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
}
@Override
@@ -229,6 +251,8 @@ public final class TaskFragmentOperation implements Parcelable {
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeTypedObject(mSurfaceTransaction, flags);
}
@NonNull
@@ -324,6 +348,22 @@ public final class TaskFragmentOperation implements Parcelable {
return mMoveToBottomIfClearWhenLaunch;
}
+ /** Returns the boolean value for this operation. */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+ * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+ * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+ * change the decor surface layers.
+ */
+ @Nullable
+ public SurfaceControl.Transaction getSurfaceTransaction() {
+ return mSurfaceTransaction;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@ public final class TaskFragmentOperation implements Parcelable {
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+ sb.append(", booleanValue=").append(mBooleanValue);
+ if (mSurfaceTransaction != null) {
+ sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+ }
sb.append('}');
return sb.toString();
@@ -358,7 +402,7 @@ public final class TaskFragmentOperation implements Parcelable {
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
- mMoveToBottomIfClearWhenLaunch);
+ mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
}
@Override
@@ -376,7 +420,9 @@ public final class TaskFragmentOperation implements Parcelable {
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
&& mDimOnTask == other.mDimOnTask
- && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+ && mBooleanValue == other.mBooleanValue
+ && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
}
@Override
@@ -414,6 +460,11 @@ public final class TaskFragmentOperation implements Parcelable {
private boolean mMoveToBottomIfClearWhenLaunch;
+ private boolean mBooleanValue;
+
+ @Nullable
+ private SurfaceControl.Transaction mSurfaceTransaction;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -505,13 +556,37 @@ public final class TaskFragmentOperation implements Parcelable {
}
/**
+ * Sets the boolean value for this operation.
+ * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+ */
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+ * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+ * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+ * transaction to change the decor surface layers.
+ */
+ @NonNull
+ public Builder setSurfaceTransaction(
+ @Nullable SurfaceControl.Transaction surfaceTransaction) {
+ mSurfaceTransaction = surfaceTransaction;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+ mSurfaceTransaction);
}
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 63a2474e70c1..ed1d434257df 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -14,3 +14,10 @@ flag {
description: "Enables desktop windowing"
bug: "304778354"
}
+
+flag {
+ name: "enable_desktop_windowing_modals_policy"
+ namespace: "lse_desktop_experience"
+ description: "Enables policy for modals activities"
+ bug: "319492844"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848705e4..82e613e18d41 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@ flag {
description: "Properties to allow apps and activities to opt-in to cover display rendering"
bug: "312530526"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "enable_wm_extensions_for_all_flag"
+ description: "Whether to enable WM Extensions for all devices"
+ bug: "306666082"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 2c493031ea8a..2db3e658530f 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -16,6 +16,8 @@
package com.android.internal.accessibility.common;
+import android.os.SystemProperties;
+
/**
* Collection of common constants for accessibility magnification.
*/
@@ -31,6 +33,7 @@ public final class MagnificationConstants {
/** Minimum supported value for magnification scale. */
public static final float SCALE_MIN_VALUE = 1.0f;
- /** Maximum supported value for magnification scale. */
- public static final float SCALE_MAX_VALUE = 8.0f;
+ /** Maximum supported value for magnification scale. Default of 8.0. */
+ public static final float SCALE_MAX_VALUE =
+ Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe070048811..d4dcec948e31 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@ class ZygoteConnection {
throw ex;
}
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
isEof = false;
}
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 8ddd4ffd3065..d67a63046723 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -113,7 +113,7 @@ public class CachingIconView extends ImageView {
}
@Nullable
- private Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+ Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
return LocalImageResolver.resolveImage(icon, getContext(), mMaxDrawableWidth,
mMaxDrawableHeight);
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 06835f00755f..6d5a96ae9a09 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -37,7 +37,6 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.Spannable;
@@ -1216,7 +1215,7 @@ public class ConversationLayout extends FrameLayout
return new ConversationHeaderData(
conversationText,
new OneToOneConversationAvatarData(
- resolveAvatarImage(conversationIcon)));
+ resolveAvatarImageForOneToOne(conversationIcon)));
}
final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
@@ -1283,18 +1282,29 @@ public class ConversationLayout extends FrameLayout
return new ConversationHeaderData(
conversationText,
- new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
- resolveAvatarImage(secondLastIcon)));
+ new GroupConversationAvatarData(resolveAvatarImageForFacePile(lastIcon),
+ resolveAvatarImageForFacePile(secondLastIcon)));
}
/**
- * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
- * are received as Icon. So, we can't make use of ImageResolver.
+ * One To One Conversation Avatars is loaded by CachingIconView(conversation icon view).
*/
@Nullable
- private Drawable resolveAvatarImage(Icon conversationIcon) {
+ private Drawable resolveAvatarImageForOneToOne(Icon conversationIcon) {
try {
- return LocalImageResolver.resolveImage(conversationIcon, getContext());
+ return mConversationIconView.loadSizeRestrictedIcon(conversationIcon);
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Group Avatar drawables are loaded by Icon.
+ */
+ @Nullable
+ private Drawable resolveAvatarImageForFacePile(Icon conversationIcon) {
+ try {
+ return conversationIcon.loadDrawable(getContext());
} catch (Exception ex) {
return null;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a0dc94f22c31..ac961ee07af4 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,6 +266,7 @@ cc_library_shared_for_libandroid_runtime {
"fd_utils.cpp",
"android_hardware_input_InputWindowHandle.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
+ "android_window_InputTransferToken.cpp",
"android_window_WindowInfosListener.cpp",
"android_window_ScreenCapture.cpp",
"jni_common.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa63f4fa03d4..9bbd19122153 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -221,6 +221,7 @@ extern int register_jni_common(JNIEnv* env);
extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
+extern int register_android_window_InputTransferToken(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1678,6 +1679,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_tracing_PerfettoDataSource),
REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
REG_JNI(register_android_tracing_PerfettoProducer),
+ REG_JNI(register_android_window_InputTransferToken),
};
/*
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 07cbaadf0580..3a1e8835c8db 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -19,24 +19,23 @@
//#define LOG_NDEBUG 0
-#include <inttypes.h>
-
-#include <nativehelper/JNIHelp.h>
-
#include <android-base/stringprintf.h>
#include <android_runtime/AndroidRuntime.h>
+#include <input/InputConsumer.h>
#include <input/InputTransport.h>
+#include <inttypes.h>
#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include <utils/Looper.h>
+
#include <variant>
#include <vector>
+
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
-
-#include <nativehelper/ScopedLocalRef.h>
-
#include "core_jni_helpers.h"
namespace android {
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
new file mode 100644
index 000000000000..60568e30ccb2
--- /dev/null
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTransferToken"
+
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
+ jmethodID ctor;
+} gInputTransferTokenClassInfo;
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jlong nativeCreateFromBinder(JNIEnv* env, jclass clazz, jobject tokenBinderObj) {
+ if (tokenBinderObj == nullptr) {
+ return 0;
+ }
+ sp<IBinder> token(ibinderForJavaObject(env, tokenBinderObj));
+ if (token == nullptr) {
+ return 0;
+ }
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make(token);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jclass clazz, jlong nativeObj, jobject parcelObj) {
+ InputTransferToken* inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->writeToParcel(parcel);
+}
+
+static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->readFromParcel(parcel);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj) {
+ sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ return javaObjectForIBinder(env, inputTransferToken->mToken);
+}
+
+InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj) {
+ if (inputTransferTokenObj != nullptr &&
+ env->IsInstanceOf(inputTransferTokenObj, gInputTransferTokenClassInfo.clazz)) {
+ return reinterpret_cast<InputTransferToken*>(
+ env->GetLongField(inputTransferTokenObj,
+ gInputTransferTokenClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
+jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken) {
+ if (inputTransferToken == nullptr || env == nullptr) {
+ return nullptr;
+ }
+
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return env->NewObject(gInputTransferTokenClassInfo.clazz, gInputTransferTokenClassInfo.ctor,
+ reinterpret_cast<jlong>(inputTransferToken));
+}
+
+static void release(InputTransferToken* inputTransferToken) {
+ inputTransferToken->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetNativeInputTransferTokenFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
+static bool nativeEquals(JNIEnv* env, jclass clazz, jlong inputTransferTokenObj1,
+ jlong inputTransferTokenObj2) {
+ sp<InputTransferToken> inputTransferToken1(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj1));
+ sp<InputTransferToken> inputTransferToken2(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj2));
+
+ return inputTransferToken1 == inputTransferToken2;
+}
+
+static const JNINativeMethod sInputTransferTokenMethods[] = {
+ // clang-format off
+ {"nativeCreate", "()J", (void*)nativeCreate},
+ {"nativeCreate", "(Landroid/os/IBinder;)J", (void*)nativeCreateFromBinder},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+ {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+ {"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+ {"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
+ {"nativeEquals", "(JJ)Z", (void*) nativeEquals},
+ // clang-format on
+};
+
+int register_android_window_InputTransferToken(JNIEnv* env) {
+ int err = RegisterMethodsOrDie(env, "android/window/InputTransferToken",
+ sInputTransferTokenMethods, NELEM(sInputTransferTokenMethods));
+ jclass inputTransferTokenClass = FindClassOrDie(env, "android/window/InputTransferToken");
+ gInputTransferTokenClassInfo.clazz = MakeGlobalRefOrDie(env, inputTransferTokenClass);
+ gInputTransferTokenClassInfo.mNativeObject =
+ GetFieldIDOrDie(env, gInputTransferTokenClassInfo.clazz, "mNativeObject", "J");
+ gInputTransferTokenClassInfo.ctor =
+ GetMethodIDOrDie(env, gInputTransferTokenClassInfo.clazz, "<init>", "(J)V");
+ return err;
+}
+
+} // namespace android \ No newline at end of file
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd50a902..e0cc055a62a6 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@ jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, j
return result;
}
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+ strerror(errno)));
+ }
+
+ return credentials.uid;
+}
+
// Read all lines from the current command into the buffer, and then reset the buffer, so
// we will start reading again at the beginning of the command, starting with the argument
// count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
fail_fn_z("Failed to retrieve session socket timeout");
}
- struct ucred credentials;
- socklen_t cred_size = sizeof credentials;
- if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
- || cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
- strerror(errno)));
+ uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+ if (peerUid != static_cast<uid_t>(expected_uid)) {
+ return JNI_FALSE;
}
-
bool first_time = true;
do {
- if (credentials.uid != static_cast<uid_t>(expected_uid)) {
- return JNI_FALSE;
- }
n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
n_buffer->reset();
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
}
}
for (;;) {
+ bool valid_session_socket = true;
// Clear buffer and get count from next command.
n_buffer->clear();
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
- } // else disconnected;
+ } else {
+ // Session socket was disconnected
+ valid_session_socket = false;
+ close(session_socket);
+ }
} else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
fail_fn_z(
CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
}
- // We've now seen either a disconnect or connect request.
- close(session_socket);
- int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ int new_fd = -1;
+ do {
+ // We've now seen either a disconnect or connect request.
+ new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ if (new_fd == -1) {
+ fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ }
+ uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+ if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+ ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+ close(new_fd);
+ new_fd = -1;
+ } else {
+ // If we still have a valid session socket, close it now
+ if (valid_session_socket) {
+ close(session_socket);
+ }
+ valid_session_socket = true;
+ }
+ } while (!valid_session_socket);
+
+ // At this point we either have a valid new connection (new_fd > 0), or
+ // an existing session socket we can poll on
if (new_fd == -1) {
- fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ // The new connection wasn't valid, and we still have an old one; retry polling
+ continue;
}
if (new_fd != session_socket) {
- // Move new_fd back to the old value, so that we don't have to change Java-level data
- // structures to reflect a change. This implicitly closes the old one.
- if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
- fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
- new_fd, session_socket, strerror(errno)));
- }
- close(new_fd); // On Linux, fd is closed even if EINTR is returned.
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd); // On Linux, fd is closed even if EINTR is returned.
}
// If we ever return, we effectively reuse the old Java ZygoteConnection.
// None of its state needs to change.
@@ -493,13 +524,6 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
session_socket, strerror(errno)));
}
- if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
- fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
- }
- if (cred_size != sizeof credentials) {
- fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
- cred_size, static_cast<int>(sizeof credentials)));
- }
}
first_time = false;
} while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/jni/include/android_runtime/android_window_InputTransferToken.h b/core/jni/include/android_runtime/android_window_InputTransferToken.h
new file mode 100644
index 000000000000..75dbe37f781f
--- /dev/null
+++ b/core/jni/include/android_runtime/android_window_InputTransferToken.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+#define _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+
+#include <gui/InputTransferToken.h>
+#include <jni.h>
+
+namespace android {
+
+extern InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj);
+
+extern jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken);
+
+} // namespace android
+
+#endif // _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75a600e..0664211062b4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6781,7 +6781,7 @@
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
-->
<permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
@@ -6916,6 +6916,12 @@
<permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to record sensitive content during media
+ projection. This is intended for on device screen recording system app.
+ @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
+ <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
+ android:protectionLevel="signature"/>
+
<!-- @SystemApi Allows an application to read install sessions
@hide This is not a third-party API (intended for system apps). -->
<permission android:name="android.permission.READ_INSTALL_SESSIONS"
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 6e804c0b428a..9ad577ad8bf6 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -181,19 +181,95 @@ a similar way.
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
<item name="android:windowFullscreen">true</item>
- <!-- Color palette Dark -->
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+ <!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
- <item name="colorForeground">@color/foreground_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+ <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
+ <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
+ <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
+ <item name="colorSurface">@color/surface_dark</item>
+ <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
+ <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
+ <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+ <item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
- <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
- <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
- <item name="colorError">@color/error_color_device_default_dark</item>
- <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
- <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
- <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
+ <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+ <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+ <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
+ <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
+ <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
+ <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
+ <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+ <item name="colorForeground">@color/foreground_device_default_dark</item>
+ <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+ <!-- Progress bar attributes -->
+ <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+ <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
+
+ <!-- Toolbar attributes -->
+ <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+ <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+ <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+ <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+ <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+ <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+ <item name="materialColorOnError">@color/system_on_error_dark</item>
+ <item name="materialColorSurface">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+ <item name="materialColorOutline">@color/system_outline_dark</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorPrimary">@color/system_primary_dark</item>
+ <item name="materialColorSecondary">@color/system_secondary_dark</item>
+ <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+ <item name="materialColorError">@color/system_error_dark</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d89f23614179..5e900f773a65 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3287,10 +3287,9 @@
-->
<attr name="enableOnBackInvokedCallback" format="boolean"/>
- <!-- Specifies permissions necessary to launch this activity via
- {@link android.content.Context#startActivity} when passing content URIs. The default
- value is {@code none}, meaning no specific permissions are required. Setting this
- attribute restricts activity invocation based on the invoker's permissions. If the
+ <!-- Specifies permissions necessary to launch this activity when passing content URIs. The
+ default value is {@code none}, meaning no specific permissions are required. Setting
+ this attribute restricts activity invocation based on the invoker's permissions. If the
invoker doesn't have the required permissions, the activity start will be denied via a
{@link java.lang.SecurityException}.
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 1925588e8904..33f37da39fea 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@ import org.junit.runner.RunWith;
import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
+@Presubmit
@SmallTest
public class AutomaticZenRuleTest {
private static final String CLASS = "android.app.AutomaticZenRule";
diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
index 625c66a4c60e..046f5ac10e36 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
@@ -20,7 +20,7 @@ import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
@@ -35,6 +35,7 @@ import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationChannelGroupTest {
private final String CLASS = "android.app.NotificationChannelGroup";
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 56ab03419b66..18209b548f94 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -46,6 +46,7 @@ import android.os.Parcel;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.MediaStore.Audio.AudioColumns;
import android.test.mock.MockContentResolver;
@@ -74,6 +75,7 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationChannelTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index bd493f41d25b..c44c1ebbed6b 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.NotificationHistory.HistoricalNotification;
import android.graphics.drawable.Icon;
import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -35,13 +36,19 @@ import java.util.List;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
+@Presubmit
public class NotificationHistoryTest {
- private HistoricalNotification getHistoricalNotification(int index) {
+ private static HistoricalNotification getHistoricalNotification(int index) {
return getHistoricalNotification("package" + index, index);
}
- private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+ private static HistoricalNotification getHistoricalNotification(String packageName, int index) {
+ return getHistoricalNotification(packageName, index, /* includeIcon= */ true);
+ }
+
+ private static HistoricalNotification getHistoricalNotification(String packageName, int index,
+ boolean includeIcon) {
String expectedChannelName = "channelName" + index;
String expectedChannelId = "channelId" + index;
int expectedUid = 1123456 + index;
@@ -65,7 +72,7 @@ public class NotificationHistoryTest {
.setPostedTimeMs(expectedPostTime)
.setTitle(expectedTitle)
.setText(expectedText)
- .setIcon(expectedIcon)
+ .setIcon(includeIcon ? expectedIcon : null)
.setConversationId(conversationId)
.build();
}
@@ -376,7 +383,8 @@ public class NotificationHistoryTest {
List<HistoricalNotification> expectedEntries = new ArrayList<>();
for (int i = 10; i >= 1; i--) {
- HistoricalNotification n = getHistoricalNotification(i);
+ HistoricalNotification n = getHistoricalNotification("packageName" + i,
+ i, /* includeIcon= */ false);
expectedEntries.add(n);
history.addNotificationToWrite(n);
}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5b0502da1bdf..9a41fe0691fa 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -85,6 +85,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
+import android.platform.test.annotations.Presubmit;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -107,6 +108,7 @@ import junit.framework.Assert;
import libcore.junit.util.compat.CoreCompatChangeRule;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -117,6 +119,7 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationTest {
private Context mContext;
@@ -768,6 +771,7 @@ public class NotificationTest {
}
@Test
+ @Ignore // TODO: b/329389261 - Restore or delete
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();
boolean colorized = false;
@@ -796,6 +800,7 @@ public class NotificationTest {
}
@Test
+ @Ignore // TODO: b/329389261 - Restore or delete
public void testColors_ensureColors_colorized_producesValidPalette_red() {
validateColorizedPaletteForColor(Color.RED);
}
@@ -1244,6 +1249,7 @@ public class NotificationTest {
}
@Test
+ @Ignore // TODO: b/329402256 - Restore or delete
public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
@@ -1257,6 +1263,7 @@ public class NotificationTest {
}
@Test
+ @Ignore // TODO: b/329402256 - Restore or delete
public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2544fcb81692..652011ba74cd 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -679,20 +679,20 @@ public class ViewRootImplTest {
}
sInstrumentation.runOnMainSync(() -> {
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
FRAME_RATE_CATEGORY_HIGH_HINT);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
});
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index b60b806f3444..a5c962412024 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,6 +23,8 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.stylus.HandwritingTestUtil.createView;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,7 @@ public class HandwritingInitiatorTest {
public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,9 +151,51 @@ public class HandwritingInitiatorTest {
// After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
// events so that the events are not dispatched to the view tree.
assertThat(onTouchEventResult2).isTrue();
- // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
- // does not need to set the cursor position.
- verify(mTestView1, never()).setSelection(anyInt());
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
+ }
+
+ @Test
+ public void onTouchEvent_startHandwriting_multipleParagraphs() {
+ // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
+ // of line 3 is offset 40.
+ mTestView1.setText("line 0 \nline 1 \nline 2 \nline 3 ");
+ mTestView1.layout(0, 0, 500, 500);
+ when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);
+
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ assertThat(onTouchEventResult1).isFalse();
+ // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+ // events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult2).isTrue();
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the paragraph containing line 2.
+ verify(mTestView1).setSelection(30);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
}
@Test
@@ -197,6 +242,7 @@ public class HandwritingInitiatorTest {
public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
if (!mInitiateWithoutConnection) {
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,9 +260,14 @@ public class HandwritingInitiatorTest {
// Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
@Test
@@ -246,6 +297,8 @@ public class HandwritingInitiatorTest {
public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
+
// The stylus down point is between mTestView1 and mTestView2, but it is within the
// extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,9 +331,14 @@ public class HandwritingInitiatorTest {
// Handwriting is started for this view since the stylus down point is closest to this
// view.
verify(mHandwritingInitiator).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 76772b7a528b..08977265667c 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,6 +16,12 @@
package android.hardware.devicestate;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -33,6 +39,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.List;
+import java.util.Set;
/**
* Unit tests for {@link DeviceStateInfo}.
@@ -44,11 +51,25 @@ import java.util.List;
public final class DeviceStateInfoTest {
private static final DeviceState DEVICE_STATE_0 = new DeviceState(
- new DeviceState.Configuration.Builder(0, "STATE_0").build());
+ new DeviceState.Configuration.Builder(0, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+ .build());
private static final DeviceState DEVICE_STATE_1 = new DeviceState(
- new DeviceState.Configuration.Builder(1, "STATE_1").build());
+ new DeviceState.Configuration.Builder(1, "STATE_1")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+ .build());
private static final DeviceState DEVICE_STATE_2 = new DeviceState(
- new DeviceState.Configuration.Builder(2, "STATE_2").build());
+ new DeviceState.Configuration.Builder(2, "STATE_2")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .build());
@Test
public void create() {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index 68de21f76dc8..78d4324ebb1a 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -93,4 +93,22 @@ public final class DeviceStateTest {
Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
}
+
+ @Test
+ public void writeToParcel_noPhysicalProperties() {
+ final DeviceState originalState = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE")
+ .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+ .build());
+
+ final Parcel parcel = Parcel.obtain();
+ originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ final DeviceState.Configuration stateConfiguration =
+ DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+ Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 245f21658b7a..701d145fe805 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -12,3 +12,4 @@ yamasani@google.com
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
per-file services.core.protolog.json = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core.protolog.pb = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438613cf..749f0e12aa03 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@ applications that come with the platform
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0231d3abd19e..1aa8af5f5e84 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2665,12 +2665,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/Task.java"
},
- "4446998544419008924": {
- "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"4037728373502324767": {
"message": "resumeNextFocusableActivityWhenRootTaskIsEmpty: %s, go home",
"level": "DEBUG",
@@ -2767,12 +2761,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
- "2088177629189452176": {
- "message": "Activity config changed during resume: %s, new next: %s",
- "level": "INFO",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/TaskFragment.java"
- },
"-8483536760290526299": {
"message": "resumeTopActivity: Resumed %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e3cf3c..d44033c72302 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@ class ActivityEmbeddingAnimationRunner {
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
+ * different split layout (ratio or direction). This can be complicated, like from horizontal to
+ * vertical split with new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@ class ActivityEmbeddingAnimationRunner {
}
// Check if the transition contains both opening and closing windows.
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
+ final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+ final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if (changingChanges.contains(change)) {
continue;
@@ -564,10 +564,30 @@ class ActivityEmbeddingAnimationRunner {
// No-op if it will be covered by the changing parent window.
continue;
}
- hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
- hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ openChanges.add(change);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ closeChanges.add(change);
+ }
+ }
+ if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+ // Only skip if the transition contains both open and close.
+ return false;
+ }
+ if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+ // Skip when there are too many windows involved.
+ return true;
+ }
+ final TransitionInfo.Change changingChange = changingChanges.get(0);
+ final TransitionInfo.Change openChange = openChanges.get(0);
+ final TransitionInfo.Change closeChange = closeChanges.get(0);
+ if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+ && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+ // Don't skip if the transition is a simple shifting without split direction or ratio
+ // change. For example, A|B -> B|C.
+ return false;
}
- return hasOpeningWindow && hasClosingWindow;
+ return true;
}
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
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 9585842a2014..4455a3caa7d9 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
@@ -1259,12 +1259,14 @@ public class BubbleController implements ConfigurationChangeListener,
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
- * This is the method to use when opening a bubble via a notification or in a state where
+ * <p>This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+ entry.getKey(), mIsStatusBarShade);
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 23bdd08e6b24..6524c96fb21a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -449,17 +449,21 @@ public class BubbleStackView extends FrameLayout
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ @NonNull MagnetizedObject<?> draggedObject) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, true);
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, false);
if (wasFlungOut) {
@@ -474,7 +478,9 @@ public class BubbleStackView extends FrameLayout
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
@NonNull MagnetizedObject<?> draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
mExpandedAnimationController.dismissDraggedOutBubble(
view /* bubble */,
mDismissView.getHeight() /* translationYBy */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b2ec0a35685..8d489e106ae1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -846,8 +846,10 @@ public abstract class WMShellBaseModule {
static ShellController provideShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+ return new ShellController(context, shellInit, shellCommandHandler,
+ displayInsetsController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb3c35b6a1e3..04f0f44d2876 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,8 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -509,6 +511,7 @@ public abstract class WMShellModule {
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@@ -518,7 +521,8 @@ public abstract class WMShellModule {
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@@ -562,6 +566,22 @@ public abstract class WMShellModule {
return new DesktopModeTaskRepository();
}
+ @WMSingleton
+ @Provides
+ static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+ ShellInit shellInit,
+ Transitions transitions,
+ DesktopModeEventLogger desktopModeEventLogger) {
+ return new DesktopModeLoggerTransitionObserver(
+ shellInit, transitions, desktopModeEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
+ static DesktopModeEventLogger provideDesktopModeEventLogger() {
+ return new DesktopModeEventLogger();
+ }
+
//
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
new file mode 100644
index 000000000000..a10c7c093c60
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+ private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+ }
+
+ // A sparse array of visible freeform tasks and taskInfos
+ private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+ // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+ // animation was cancelled, we restore these tasks to calculate the post-Transition state
+ private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+ // The instanceId for the current logging session
+ private var loggerInstanceId: InstanceId? = null
+
+ private val isSessionActive: Boolean
+ get() = loggerInstanceId != null
+
+ private fun setSessionInactive() {
+ loggerInstanceId = null
+ }
+
+ fun onInit() {
+ transitions.registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // this was a new recents animation
+ if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Recents animation running, saving tasks for later"
+ )
+ // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+ // recents animation
+
+ // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+ // if the user ends up at home, we need to update the visible freeform tasks
+ // if the user cancels the animation, the subsequent transition is NONE
+ // if the user opens a new task, the subsequent transition is OPEN with flag
+ tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+ }
+
+ // figure out what the new state of freeform tasks would be post transition
+ var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+ // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+ // that's the case, we might have accidentally logged a session exit and would need to
+ // revaluate again. Add all the tasks back.
+ // This will start a new desktop mode session.
+ if (
+ info.type == WindowManager.TRANSIT_NONE &&
+ info.flags == 0 &&
+ tasksSavedForRecents.isNotEmpty()
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Canceled recents animation, restoring tasks"
+ )
+ // restore saved tasks in the updated set and clear for next use
+ postTransitionVisibleFreeformTasks += tasksSavedForRecents
+ tasksSavedForRecents.clear()
+ }
+
+ // identify if we need to log any changes and update the state of visible freeform tasks
+ identifyLogEventAndUpdateState(
+ transitionInfo = info,
+ preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+ postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+ )
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {}
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+ private fun getPostTransitionVisibleFreeformTaskInfos(
+ info: TransitionInfo
+ ): SparseArray<TaskInfo> {
+ // device is sleeping, so no task will be visible anymore
+ if (info.type == WindowManager.TRANSIT_SLEEP) {
+ return SparseArray()
+ }
+
+ // filter changes involving freeform tasks or tasks that were cached in previous state
+ val changesToFreeformWindows =
+ info.changes
+ .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+ .filter {
+ it.requireTaskInfo().isFreeformWindow() ||
+ visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+ }
+
+ val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+ // start off by adding all existing tasks
+ postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+ // the combined set of taskInfos we are interested in this transition change
+ for (change in changesToFreeformWindows) {
+ val taskInfo = change.requireTaskInfo()
+
+ // check if this task existed as freeform window in previous cached state and it's now
+ // changing window modes
+ if (
+ visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+ visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+ !taskInfo.isFreeformWindow()
+ ) {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ // no need to evaluate new visibility of this task, since it's no longer a freeform
+ // window
+ continue
+ }
+
+ // check if the task is visible after this change, otherwise remove it
+ if (isTaskVisibleAfterChange(change)) {
+ postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+ } else {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ }
+ }
+
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: taskInfo map after processing changes %s",
+ postTransitionFreeformTasks.size()
+ )
+
+ return postTransitionFreeformTasks
+ }
+
+ /**
+ * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+ * change is processed
+ */
+ private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+ when {
+ TransitionUtil.isOpeningType(change.mode) -> true
+ TransitionUtil.isClosingType(change.mode) -> false
+ // change mode TRANSIT_CHANGE is only for visible to visible transitions
+ change.mode == WindowManager.TRANSIT_CHANGE -> true
+ else -> false
+ }
+
+ /**
+ * Log the appropriate log event based on the new state of TasksInfos and previously cached
+ * state and update it
+ */
+ private fun identifyLogEventAndUpdateState(
+ transitionInfo: TransitionInfo,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ if (
+ postTransitionVisibleFreeformTasks.isEmpty() &&
+ preTransitionVisibleFreeformTasks.isNotEmpty() &&
+ isSessionActive
+ ) {
+ // Sessions is finishing, log task updates followed by an exit event
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+
+ desktopModeEventLogger.logSessionExit(
+ loggerInstanceId!!.id,
+ getExitReason(transitionInfo)
+ )
+
+ setSessionInactive()
+ } else if (
+ postTransitionVisibleFreeformTasks.isNotEmpty() &&
+ preTransitionVisibleFreeformTasks.isEmpty() &&
+ !isSessionActive
+ ) {
+ // Session is starting, log enter event followed by task updates
+ loggerInstanceId = idSequence.newInstanceId()
+ desktopModeEventLogger.logSessionEnter(
+ loggerInstanceId!!.id,
+ getEnterReason(transitionInfo)
+ )
+
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ } else if (isSessionActive) {
+ // Session is neither starting, nor finishing, log task updates if there are any
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ }
+
+ // update the state to the new version
+ visibleFreeformTaskInfos.clear()
+ visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+ }
+
+ // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+ /** Compare the old and new state of taskInfos and identify and log the changes */
+ private fun identifyAndLogTaskUpdates(
+ sessionId: Int,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ // find new tasks that were added
+ postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+
+ // find old tasks that were removed
+ preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+ }
+
+ // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+ private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+ val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+ // add task x, y if available
+ taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+ return taskUpdate
+ }
+
+ /** Get [EnterReason] for this session enter */
+ private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+ // TODO(b/326231756) - Add support for missing enter reasons
+ return when (transitionInfo.type) {
+ WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+ Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+ WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ else -> EnterReason.UNKNOWN_ENTER
+ }
+ }
+
+ /** Get [ExitReason] for this session exit */
+ private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+ // TODO(b/326231756) - Add support for missing exit reasons
+ return when {
+ transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+ transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+ transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+ transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+ else -> ExitReason.UNKNOWN_EXIT
+ }
+ }
+
+ /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+ @VisibleForTesting
+ fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+ visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+ }
+
+ @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+ @VisibleForTesting
+ fun setLoggerSessionId(id: Int) {
+ loggerInstanceId = InstanceId.fakeInstanceId(id)
+ }
+
+ private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+ return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+ }
+
+ private fun TaskInfo.isFreeformWindow(): Boolean {
+ return this.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ private fun TransitionInfo.isRecentsTransition(): Boolean {
+ return this.type == WindowManager.TRANSIT_TO_FRONT &&
+ this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed1587055..6a3c8d2f599a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@ public class DesktopModeVisualIndicator {
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
- ? 2 * layout.stableInsets().top
- : mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ : 2 * layout.stableInsets().top;
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
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 95237c38f309..4a3130f3c54b 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
@@ -78,6 +78,7 @@ import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
@@ -102,6 +103,7 @@ class DesktopTasksController(
ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@@ -959,7 +961,8 @@ class DesktopTasksController(
}
/**
- * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+ * that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
@@ -970,7 +973,8 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect
+ taskBounds: Rect,
+ validDragArea: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -993,10 +997,21 @@ class DesktopTasksController(
releaseVisualIndicator()
snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward and perform a
+ // transaction to set bounds.
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, validDragArea)) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, taskBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ }
releaseVisualIndicator()
}
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+ "a freeform task.")
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4b3b9a..86c8f04f8138 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -436,7 +436,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
+ } else {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7f16c5e3592e..af11ebc515d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -45,6 +46,8 @@ public class SplitScreenShellCommandHandler implements
return runSetSideStagePosition(args, pw);
case "switchSplitPosition":
return runSwitchSplitPosition();
+ case "exitSplitScreen":
+ return runExitSplitScreen(args, pw);
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -91,6 +94,17 @@ public class SplitScreenShellCommandHandler implements
return true;
}
+ private boolean runExitSplitScreen(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = Integer.parseInt(args[1]);
+ mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN);
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -101,5 +115,7 @@ public class SplitScreenShellCommandHandler implements
pw.println(prefix + " Sets the position of the side-stage.");
pw.println(prefix + "switchSplitPosition");
pw.println(prefix + " Reverses the split.");
+ pw.println(prefix + "exitSplitScreen <taskId>");
+ pw.println(prefix + " Exits split screen and leaves the provided split task on top.");
}
}
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 9dd4c193a006..36368df9af36 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
@@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -1450,6 +1451,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
+ /** Exits split screen with legacy transition */
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1469,6 +1471,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
+ /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1546,6 +1549,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+ final int stage = getStageOfTask(toTopTaskId);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(stage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+ }
+
/**
* Overridden by child classes.
*/
@@ -1611,6 +1622,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// User has used a keyboard shortcut to go back to fullscreen from split
case EXIT_REASON_DESKTOP_MODE:
// One of the children enters desktop mode
+ case EXIT_REASON_UNKNOWN:
+ // Unknown reason
return true;
default:
return false;
@@ -2776,7 +2789,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ " with " + taskInfo.taskId + " before startAnimation().");
record.addRecord(stage, true, taskInfo.taskId);
}
- } else if (isClosingType(change.getMode())) {
+ } else if (change.getMode() == TRANSIT_CLOSE) {
if (stage.containsTask(taskInfo.taskId)) {
record.addRecord(stage, false, taskInfo.taskId);
Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 1ce87ef73fd7..4465aef0258d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.startingsurface;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
@@ -270,21 +271,18 @@ public class StartingSurfaceDrawer {
@Override
public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (immediately) {
+ if (immediately
+ // Show the latest content as soon as possible for unlocking to home.
+ || mActivityType == ACTIVITY_TYPE_HOME
+ || info.deferRemoveMode == DEFER_MODE_NONE) {
removeImmediately();
- } else {
- scheduleRemove(info.deferRemoveForImeMode);
- return false;
+ return true;
}
- return true;
+ scheduleRemove(info.deferRemoveMode);
+ return false;
}
void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
final long delayRemovalTime;
switch (deferRemoveForImeMode) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 000000000000..a94f80241d4f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+ /**
+ * Called when the ime bounds change.
+ */
+ default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+ /**
+ * Called when the IME visibility change.
+ */
+ default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e218a8a..2f6edc226c45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.SurfaceControlRegistry;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -57,6 +64,7 @@ public class ShellController {
private final ShellInit mShellInit;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@ public class ShellController {
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+ new ConcurrentHashMap<>();
private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
new ArrayMap<>();
@@ -73,20 +83,53 @@ public class ShellController {
private Configuration mLastConfiguration;
+ private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+ private InsetsState mInsetsState = new InsetsState();
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState == insetsState) {
+ return;
+ }
+
+ InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ boolean wasVisible = (oldSource != null && oldSource.isVisible());
+ Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+ InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ boolean isVisible = (newSource != null && newSource.isVisible());
+ Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+ if (wasVisible != isVisible) {
+ onImeVisibilityChanged(isVisible);
+ }
+
+ if (newFrame != null && !newFrame.equals(oldFrame)) {
+ onImeBoundsChanged(newFrame);
+ }
+
+ mInsetsState = insetsState;
+ }
+ };
+
public ShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
ShellExecutor mainExecutor) {
mContext = context;
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(
+ mContext.getDisplayId(), mInsetsChangeListener);
}
/**
@@ -259,6 +302,25 @@ public class ShellController {
}
}
+ @VisibleForTesting
+ void onImeBoundsChanged(Rect bounds) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeBoundsChanged(
+ mContext.getDisplayId(), bounds)));
+ }
+
+ @VisibleForTesting
+ void onImeVisibilityChanged(boolean isShowing) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+ isShowing);
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeVisibilityChanged(
+ mContext.getDisplayId(), isShowing)));
+ }
+
private void handleInit() {
SurfaceControlRegistry.createProcessInstance(mContext);
mShellInit.init();
@@ -329,6 +391,19 @@ public class ShellController {
}
@Override
+ public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+ mDisplayImeChangeListeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+ mDisplayImeChangeListeners.remove(listener);
+ }
+
+ @Override
public boolean handleCommand(String[] args, PrintWriter pw) {
try {
boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11ef54e..bd1c64a0d182 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@ public interface ShellInterface {
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
/**
+ * Registers a DisplayImeChangeListener to monitor for changes on Ime
+ * position and visibility.
+ */
+ default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {}
+
+ /**
+ * Removes a registered DisplayImeChangeListener.
+ */
+ default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+ /**
* Handles a shell command.
*/
default boolean handleCommand(final String[] args, PrintWriter pw) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 4ea71490798c..5b402a5a7d53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -212,6 +212,7 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
switch (mType) {
case TYPE_RECENTS_DURING_DESKTOP:
case TYPE_RECENTS_DURING_SPLIT:
+ case TYPE_RECENTS_DURING_KEYGUARD:
mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
break;
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 7a50814f0275..564e716c7378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -31,42 +31,42 @@ class KtProtoLog {
companion object {
/** @see [com.android.internal.protolog.common.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.d(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.v(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.i(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.w(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.e(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.wtf(group.tag, String.format(messageString, *args))
}
}
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 b2eeea7048bc..c59a1b452303 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
@@ -19,9 +19,11 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -186,7 +188,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
final FluidResizeTaskPositioner taskPositioner =
new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
- mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+ mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+ mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+ if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskInfo.token, newTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601c6a27..9a48922fd238 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
R.dimen.caption_left_buttons_width);
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 98ff0eed9c11..918cefbee9f4 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
@@ -639,7 +639,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds);
+ newTaskBounds, decoration.calculateValidDragArea());
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -867,8 +867,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
if (mTransitionDragActive) {
// Do not create an indicator at all if we're not past transition height.
- if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
- .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ DisplayLayout layout = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+ if (ev.getRawY() < 2 * layout.stableInsets().top
&& mMoveToDesktopAnimator == null) {
return;
}
@@ -1086,18 +1087,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback;
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
- mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions, transitionAreaHeight);
+ mDragStartListener, mTransitions);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
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 c9669a788994..4c9e17155625 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.windowingModeToString;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -499,6 +500,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* Determine valid drag area for this task based on elements in the app chip.
*/
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54088d1..82c399ad8152 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@ public class DragPositioningCallbackUtility {
t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
- private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@ public class DragPositioningCallbackUtility {
}
/**
- * Calculates the new position of the top edge of the task and returns true if it is below the
- * disallowed area.
- *
- * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
- * should not finalize the bounds using WCT#setBounds
- * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param repositionStartPoint initial input coordinate
- * @param y the y position of the motion event
- * @return true if the top of the task is below the disallowed area
+ * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+ * drag area.
+ * @param repositionTaskBounds bounds determined by task positioner
+ * @param validDragArea the area that task must be positioned inside
+ * @return whether bounds were modified
*/
- static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
- Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
- final float deltaY = y - repositionStartPoint.y;
- final float topPosition = taskBoundsAtDragStart.top + deltaY;
- return topPosition > disallowedAreaForEndBoundsHeight;
- }
-
- /**
- * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
- * valid drag area.
- */
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
- PointF repositionStartPoint, float x, float y, Rect validDragArea) {
- updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
- x, y);
- snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
- }
-
- private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
// If we were never supplied a valid drag area, do not restrict movement.
// Otherwise, we restrict deltas to keep task position inside the Rect.
- if (validDragArea.width() == 0) return;
+ if (validDragArea.width() == 0) return false;
+ boolean result = false;
if (repositionTaskBounds.left < validDragArea.left) {
repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ result = true;
} else if (repositionTaskBounds.left > validDragArea.right) {
repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ result = true;
}
if (repositionTaskBounds.top < validDragArea.top) {
repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ result = true;
} else if (repositionTaskBounds.top > validDragArea.bottom) {
repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ result = true;
}
+ return result;
}
private static float getMinWidth(DisplayController displayController,
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 6bfc7cdcb33e..6f8b3d5aaaad 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
@@ -60,9 +60,6 @@ class FluidResizeTaskPositioner implements DragPositioningCallback,
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
@@ -70,11 +67,9 @@ class FluidResizeTaskPositioner implements DragPositioningCallback,
@Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
- WindowDecoration windowDecoration, DisplayController displayController,
- int disallowedAreaForEndBoundsHeight) {
+ WindowDecoration windowDecoration, DisplayController displayController) {
this(taskOrganizer, transitions, windowDecoration, displayController,
- dragStartListener -> {}, SurfaceControl.Transaction::new,
- disallowedAreaForEndBoundsHeight);
+ dragStartListener -> {}, SurfaceControl.Transaction::new);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@ class FluidResizeTaskPositioner implements DragPositioningCallback,
WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier) {
mTaskOrganizer = taskOrganizer;
mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -157,14 +150,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback,
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
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 5c69d5542227..c12a93edcaf3 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
@@ -54,9 +54,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+ SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -151,13 +145,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
// won't be called.
resetVeilIfVisible();
}
- } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mDesktopWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbcb4eed..4a4c5e860bb2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@ import com.android.wm.shell.WindowManagerShellWrapper
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@ class BubbleViewInfoTest : ShellTestCase() {
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mock<DisplayInsetsController>(), mainExecutor)
bubblePositioner = BubblePositioner(context, windowManager)
val bubbleData =
BubbleData(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
new file mode 100644
index 000000000000..65117f7e9eea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeEventLogger::class.java)
+ .mockStatic(DesktopModeStatus::class.java).build()!!
+
+ @Mock
+ lateinit var testExecutor: ShellExecutor
+ @Mock
+ private lateinit var mockShellInit: ShellInit
+ @Mock
+ private lateinit var transitions: Transitions
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+ transitionObserver = DesktopModeLoggerTransitionObserver(
+ mockShellInit, transitions, desktopModeEventLogger)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ val initRunnableCaptor = ArgumentCaptor.forClass(
+ Runnable::class.java)
+ verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+ same(transitionObserver))
+ initRunnableCaptor.value.run()
+ } else {
+ transitionObserver.onInit()
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions)
+ .registerObserver(same(
+ transitionObserver))
+ }
+
+ @Test
+ fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_FREEFORM_INTENT))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo = TransitionInfoBuilder(
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_DRAG))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+ val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ taskInfo.isVisibleRequested = true
+ val change = createChange(TRANSIT_CHANGE, taskInfo)
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.SCREEN_ON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.SCREEN_OFF))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.DRAG_TO_EXIT))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.TASK_FINISHED))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ val sessionId = 1
+ // add a freeform task to an existing session
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ val sessionId = 1
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ val sessionId = 1
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+ }
+
+ /**
+ * Simulate calling the onTransitionReady() method
+ */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock(IBinder::class.java)
+ val startT = mock(
+ SurfaceControl.Transaction::class.java)
+ val finishT = mock(
+ SurfaceControl.Transaction::class.java)
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ companion object {
+ fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = taskId
+ taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+ return taskInfo
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change = Change(
+ WindowContainerToken(mock(
+ IWindowContainerToken::class.java)),
+ mock(SurfaceControl::class.java))
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce8bf53..bd39aa6ace42 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- 2 * STABLE_INSETS.top))
+ transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 0136751d8c9a..5df9dd38a75d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -87,6 +87,7 @@ import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.quality.Strictness
@@ -113,6 +114,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -163,6 +165,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
mToggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver,
launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 3384509f1da9..d38fc6cb6418 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -129,7 +129,7 @@ public class PipControllerTest extends ShellTestCase {
}).when(mMockExecutor).execute(any());
mShellInit = spy(new ShellInit(mMockExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
- mMockExecutor));
+ mMockDisplayInsetsController, mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11e9004..41a4e8d503c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97ed333b..3c387f0d7c34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c40811811..ff76a2f13527 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@ import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@ public class StartingWindowControllerTests extends ShellTestCase {
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
+ private @Mock DisplayInsetsController mDisplayInsetsController;
private @Mock ShellCommandHandler mShellCommandHandler;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@ public class StartingWindowControllerTests extends ShellTestCase {
doReturn(super.mContext.getResources()).when(mContext).getResources();
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mController = new StartingWindowController(mContext, mShellInit, mShellController,
mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c34b29d..6292018ba35d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,8 +36,8 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
import org.junit.Before;
@@ -63,12 +64,15 @@ public class ShellControllerTest extends ShellTestCase {
private ShellCommandHandler mShellCommandHandler;
@Mock
private Context mTestUserContext;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
private TestUserChangeListener mUserChangeListener;
+ private TestDisplayImeChangeListener mDisplayImeChangeListener;
@Before
@@ -77,8 +81,10 @@ public class ShellControllerTest extends ShellTestCase {
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mDisplayImeChangeListener = new TestDisplayImeChangeListener();
mExecutor = new TestShellExecutor();
- mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+ mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -130,6 +136,45 @@ public class ShellControllerTest extends ShellTestCase {
}
@Test
+ public void testAddDisplayImeChangeListener_ensureCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ final Rect bounds = new Rect(10, 20, 30, 40);
+ mController.onImeBoundsChanged(bounds);
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+
+ assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+ assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ assertTrue(mDisplayImeChangeListener.lastVisibility);
+ }
+
+ @Test
+ public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ }
+
+ @Test
+ public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
@@ -457,4 +502,23 @@ public class ShellControllerTest extends ShellTestCase {
lastUserProfiles = profiles;
}
}
+
+ private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+ public int boundsChanged = 0;
+ public Rect lastBounds;
+ public int visibilityChanged = 0;
+ public boolean lastVisibility = false;
+
+ @Override
+ public void onImeBoundsChanged(int displayId, Rect bounds) {
+ boundsChanged++;
+ lastBounds = bounds;
+ }
+
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ visibilityChanged++;
+ lastVisibility = isShowing;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be7186b1e..e6fabcfec58a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@ class DragPositioningCallbackUtilityTest {
DISPLAY_BOUNDS.right - 100,
DISPLAY_BOUNDS.bottom - 100)
- DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
- startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
validDragArea)
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
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 de6903d9a06a..ce7b63322b4a 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
@@ -125,8 +125,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
- mockTransactionFactory,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransactionFactory
)
}
@@ -576,31 +575,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
})
}
- @Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockTransitions, never()).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }}, eq(taskPositioner))
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }}, eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }}, eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
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 86253f35a51d..7f6e538f0bbf 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
@@ -138,8 +138,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransitions
)
}
@@ -355,68 +354,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top }},
- eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left }},
- eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
index 37a7d74330e9..5ef7acdaf0fa 100644
--- a/libs/hwui/Mesh.cpp
+++ b/libs/hwui/Mesh.cpp
@@ -21,6 +21,8 @@
#include "SafeMath.h"
+namespace android {
+
static size_t min_vcount_for_mode(SkMesh::Mode mode) {
switch (mode) {
case SkMesh::Mode::kTriangles:
@@ -28,6 +30,7 @@ static size_t min_vcount_for_mode(SkMesh::Mode mode) {
case SkMesh::Mode::kTriangleStrip:
return 3;
}
+ return 1;
}
// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
@@ -36,29 +39,30 @@ std::tuple<bool, SkString> Mesh::validate() {
if (!mMeshSpec) {
FAIL_MESH_VALIDATE("MeshSpecification is required.");
}
- if (mVertexBufferData.empty()) {
+ if (mBufferData->vertexData().empty()) {
FAIL_MESH_VALIDATE("VertexBuffer is required.");
}
- auto meshStride = mMeshSpec->stride();
- auto meshMode = SkMesh::Mode(mMode);
+ size_t vertexStride = mMeshSpec->stride();
+ size_t vertexCount = mBufferData->vertexCount();
+ size_t vertexOffset = mBufferData->vertexOffset();
SafeMath sm;
- size_t vsize = sm.mul(meshStride, mVertexCount);
- if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+ size_t vertexSize = sm.mul(vertexStride, vertexCount);
+ if (sm.add(vertexSize, vertexOffset) > mBufferData->vertexData().size()) {
FAIL_MESH_VALIDATE(
"The vertex buffer offset and vertex count reads beyond the end of the"
" vertex buffer.");
}
- if (mVertexOffset % meshStride != 0) {
+ if (vertexOffset % vertexStride != 0) {
FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
- mVertexOffset, meshStride);
+ vertexOffset, vertexStride);
}
if (size_t uniformSize = mMeshSpec->uniformSize()) {
- if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+ if (!mUniformBuilder.fUniforms || mUniformBuilder.fUniforms->size() < uniformSize) {
FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
- mBuilder->fUniforms->size(), uniformSize);
+ mUniformBuilder.fUniforms->size(), uniformSize);
}
}
@@ -69,29 +73,33 @@ std::tuple<bool, SkString> Mesh::validate() {
case SkMesh::Mode::kTriangleStrip:
return "triangle-strip";
}
+ return "unknown";
};
- if (!mIndexBufferData.empty()) {
- if (mIndexCount < min_vcount_for_mode(meshMode)) {
+
+ size_t indexCount = mBufferData->indexCount();
+ size_t indexOffset = mBufferData->indexOffset();
+ if (!mBufferData->indexData().empty()) {
+ if (indexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), indexCount);
}
- size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
- if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+ size_t isize = sm.mul(sizeof(uint16_t), indexCount);
+ if (sm.add(isize, indexOffset) > mBufferData->indexData().size()) {
FAIL_MESH_VALIDATE(
"The index buffer offset and index count reads beyond the end of the"
" index buffer.");
}
// If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
- if (!SkIsAlign2(mIndexOffset)) {
+ if (!SkIsAlign2(indexOffset)) {
FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
}
} else {
- if (mVertexCount < min_vcount_for_mode(meshMode)) {
+ if (vertexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), vertexCount);
}
- LOG_ALWAYS_FATAL_IF(mIndexCount != 0);
- LOG_ALWAYS_FATAL_IF(mIndexOffset != 0);
+ LOG_ALWAYS_FATAL_IF(indexCount != 0);
+ LOG_ALWAYS_FATAL_IF(indexOffset != 0);
}
if (!sm.ok()) {
@@ -100,3 +108,5 @@ std::tuple<bool, SkString> Mesh::validate() {
#undef FAIL_MESH_VALIDATE
return {true, {}};
}
+
+} // namespace android
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 69fda34afc78..8c6ca9758479 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -25,6 +25,8 @@
#include <utility>
+namespace android {
+
class MeshUniformBuilder {
public:
struct MeshUniform {
@@ -103,111 +105,170 @@ private:
sk_sp<SkMeshSpecification> fMeshSpec;
};
-class Mesh {
+// Storage for CPU and GPU copies of the vertex and index data of a mesh.
+class MeshBufferData {
public:
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
- , mVertexOffset(vertexOffset)
- , mBuilder(std::move(builder))
- , mBounds(bounds) {}
-
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
+ MeshBufferData(std::vector<uint8_t> vertexData, int32_t vertexCount, int32_t vertexOffset,
+ std::vector<uint8_t> indexData, int32_t indexCount, int32_t indexOffset)
+ : mVertexCount(vertexCount)
, mVertexOffset(vertexOffset)
- , mIndexBufferData(std::move(indexBuffer))
, mIndexCount(indexCount)
, mIndexOffset(indexOffset)
- , mBuilder(std::move(builder))
- , mBounds(bounds) {}
-
- Mesh(Mesh&&) = default;
+ , mVertexData(std::move(vertexData))
+ , mIndexData(std::move(indexData)) {}
- Mesh& operator=(Mesh&&) = default;
-
- [[nodiscard]] std::tuple<bool, SkString> validate();
-
- void updateSkMesh(GrDirectContext* context) const {
- GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
- if (context) {
- genId = context->directContextID();
+ void updateBuffers(GrDirectContext* context) const {
+ GrDirectContext::DirectContextID currentId = context == nullptr
+ ? GrDirectContext::DirectContextID()
+ : context->directContextID();
+ if (currentId == mSkiaBuffers.fGenerationId && mSkiaBuffers.fVertexBuffer != nullptr) {
+ // Nothing to update since the Android API does not support partial updates yet.
+ return;
}
- if (mIsDirty || genId != mGenerationId) {
- auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+ mSkiaBuffers.fVertexBuffer =
#ifdef __ANDROID__
- auto vb = SkMeshes::MakeVertexBuffer(context,
- vertexData,
- mVertexBufferData.size());
+ SkMeshes::MakeVertexBuffer(context, mVertexData.data(), mVertexData.size());
#else
- auto vb = SkMeshes::MakeVertexBuffer(vertexData,
- mVertexBufferData.size());
+ SkMeshes::MakeVertexBuffer(mVertexData.data(), mVertexData.size());
#endif
- auto meshMode = SkMesh::Mode(mMode);
- if (!mIndexBufferData.empty()) {
- auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+ if (mIndexCount != 0) {
+ mSkiaBuffers.fIndexBuffer =
#ifdef __ANDROID__
- auto ib = SkMeshes::MakeIndexBuffer(context,
- indexData,
- mIndexBufferData.size());
+ SkMeshes::MakeIndexBuffer(context, mIndexData.data(), mIndexData.size());
#else
- auto ib = SkMeshes::MakeIndexBuffer(indexData,
- mIndexBufferData.size());
+ SkMeshes::MakeIndexBuffer(mIndexData.data(), mIndexData.size());
#endif
- mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
- .mesh;
- } else {
- mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
- mBounds)
- .mesh;
- }
- mIsDirty = false;
- mGenerationId = genId;
}
+ mSkiaBuffers.fGenerationId = currentId;
}
- SkMesh& getSkMesh() const {
- LOG_FATAL_IF(mIsDirty,
- "Attempt to obtain SkMesh when Mesh is dirty, did you "
- "forget to call updateSkMesh with a GrDirectContext? "
- "Defensively creating a CPU mesh");
- return mMesh;
- }
+ SkMesh::VertexBuffer* vertexBuffer() const { return mSkiaBuffers.fVertexBuffer.get(); }
+
+ sk_sp<SkMesh::VertexBuffer> refVertexBuffer() const { return mSkiaBuffers.fVertexBuffer; }
+ int32_t vertexCount() const { return mVertexCount; }
+ int32_t vertexOffset() const { return mVertexOffset; }
- void markDirty() { mIsDirty = true; }
+ sk_sp<SkMesh::IndexBuffer> refIndexBuffer() const { return mSkiaBuffers.fIndexBuffer; }
+ int32_t indexCount() const { return mIndexCount; }
+ int32_t indexOffset() const { return mIndexOffset; }
- MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+ const std::vector<uint8_t>& vertexData() const { return mVertexData; }
+ const std::vector<uint8_t>& indexData() const { return mIndexData; }
private:
- sk_sp<SkMeshSpecification> mMeshSpec;
- int mMode = 0;
+ struct CachedSkiaBuffers {
+ sk_sp<SkMesh::VertexBuffer> fVertexBuffer;
+ sk_sp<SkMesh::IndexBuffer> fIndexBuffer;
+ GrDirectContext::DirectContextID fGenerationId = GrDirectContext::DirectContextID();
+ };
+
+ mutable CachedSkiaBuffers mSkiaBuffers;
+ int32_t mVertexCount = 0;
+ int32_t mVertexOffset = 0;
+ int32_t mIndexCount = 0;
+ int32_t mIndexOffset = 0;
+ std::vector<uint8_t> mVertexData;
+ std::vector<uint8_t> mIndexData;
+};
- std::vector<uint8_t> mVertexBufferData;
- size_t mVertexCount = 0;
- size_t mVertexOffset = 0;
+class Mesh {
+public:
+ // A snapshot of the mesh for use by the render thread.
+ //
+ // After a snapshot is taken, future uniform changes to the original Mesh will not modify the
+ // uniforms returned by makeSkMesh.
+ class Snapshot {
+ public:
+ Snapshot() = delete;
+ Snapshot(const Snapshot&) = default;
+ Snapshot(Snapshot&&) = default;
+ Snapshot& operator=(const Snapshot&) = default;
+ Snapshot& operator=(Snapshot&&) = default;
+ ~Snapshot() = default;
- std::vector<uint8_t> mIndexBufferData;
- size_t mIndexCount = 0;
- size_t mIndexOffset = 0;
+ const SkMesh& getSkMesh() const {
+ SkMesh::VertexBuffer* vertexBuffer = mBufferData->vertexBuffer();
+ LOG_FATAL_IF(vertexBuffer == nullptr,
+ "Attempt to obtain SkMesh when vertexBuffer has not been created, did you "
+ "forget to call MeshBufferData::updateBuffers with a GrDirectContext?");
+ if (vertexBuffer != mMesh.vertexBuffer()) mMesh = makeSkMesh();
+ return mMesh;
+ }
- std::unique_ptr<MeshUniformBuilder> mBuilder;
- SkRect mBounds{};
+ private:
+ friend class Mesh;
- mutable SkMesh mMesh{};
- mutable bool mIsDirty = true;
- mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+ Snapshot(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode,
+ std::shared_ptr<const MeshBufferData> bufferData, sk_sp<const SkData> uniforms,
+ const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::move(bufferData))
+ , mUniforms(std::move(uniforms))
+ , mBounds(bounds) {}
+
+ SkMesh makeSkMesh() const {
+ const MeshBufferData& d = *mBufferData;
+ if (d.indexCount() != 0) {
+ return SkMesh::MakeIndexed(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), d.refIndexBuffer(), d.indexCount(),
+ d.indexOffset(), mUniforms,
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
+ .mesh;
+ }
+ return SkMesh::Make(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), mUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
+ .mesh;
+ }
+
+ mutable SkMesh mMesh;
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ SkMesh::Mode mMode;
+ std::shared_ptr<const MeshBufferData> mBufferData;
+ sk_sp<const SkData> mUniforms;
+ SkRect mBounds;
+ };
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, const SkRect& bounds)
+ : Mesh(std::move(meshSpec), mode, std::move(vertexData), vertexCount, vertexOffset,
+ /* indexData = */ {}, /* indexCount = */ 0, /* indexOffset = */ 0, bounds) {}
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, std::vector<uint8_t> indexData,
+ int32_t indexCount, int32_t indexOffset, const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::make_shared<MeshBufferData>(std::move(vertexData), vertexCount,
+ vertexOffset, std::move(indexData),
+ indexCount, indexOffset))
+ , mUniformBuilder(mMeshSpec)
+ , mBounds(bounds) {}
+
+ Mesh(Mesh&&) = default;
+
+ Mesh& operator=(Mesh&&) = default;
+
+ [[nodiscard]] std::tuple<bool, SkString> validate();
+
+ std::shared_ptr<const MeshBufferData> refBufferData() const { return mBufferData; }
+
+ Snapshot takeSnapshot() const {
+ return Snapshot(mMeshSpec, mMode, mBufferData, mUniformBuilder.fUniforms, mBounds);
+ }
+
+ MeshUniformBuilder* uniformBuilder() { return &mUniformBuilder; }
+
+private:
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ SkMesh::Mode mMode;
+ std::shared_ptr<MeshBufferData> mBufferData;
+ MeshUniformBuilder mUniformBuilder;
+ SkRect mBounds;
};
+
+} // namespace android
+
#endif // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 54aef55f8b90..d0263798d2c2 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -573,9 +573,9 @@ struct DrawSkMesh final : Op {
struct DrawMesh final : Op {
static const auto kType = Type::DrawMesh;
DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
- : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+ : mesh(mesh.takeSnapshot()), blender(std::move(blender)), paint(paint) {}
- const Mesh& mesh;
+ Mesh::Snapshot mesh;
sk_sp<SkBlender> blender;
SkPaint paint;
@@ -1296,14 +1296,5 @@ void RecordingCanvas::drawWebView(skiapipeline::FunctorDrawable* drawable) {
fDL->drawWebView(drawable);
}
-[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
- LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
- if (meshWrapper) {
- return meshWrapper->getSkMesh();
- } else {
- return *mesh;
- }
-}
-
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 965264f31119..f86785274224 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,11 +41,12 @@
enum class SkBlendMode;
class SkRRect;
-class Mesh;
namespace android {
-namespace uirenderer {
+class Mesh;
+
+namespace uirenderer {
namespace skiapipeline {
class FunctorDrawable;
}
@@ -68,18 +69,6 @@ struct DisplayListOp {
static_assert(sizeof(DisplayListOp) == 4);
-class DrawMeshPayload {
-public:
- explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
- explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
-
- [[nodiscard]] const SkMesh& getSkMesh() const;
-
-private:
- const SkMesh* mesh = nullptr;
- const Mesh* meshWrapper = nullptr;
-};
-
struct DrawImagePayload {
explicit DrawImagePayload(Bitmap& bitmap)
: image(bitmap.makeImage()), palette(bitmap.palette()) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 0b739c361d64..72e83afbd96f 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -596,8 +596,8 @@ void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Pain
if (recordingContext) {
context = recordingContext->asDirectContext();
}
- mesh.updateSkMesh(context);
- mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+ mesh.refBufferData()->updateBuffers(context);
+ mCanvas->drawMesh(mesh.takeSnapshot().getSkMesh(), blender, paint);
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 14b4f584f0f3..4eb6918d7e9a 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,7 +34,6 @@ class SkCanvasState;
class SkRRect;
class SkRuntimeShaderBuilder;
class SkVertices;
-class Mesh;
namespace minikin {
class Font;
@@ -61,6 +60,7 @@ typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
class AnimatedImageDrawable;
class Bitmap;
+class Mesh;
class Paint;
struct Typeface;
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 5cb43e54e499..3109de5055ca 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -38,8 +38,8 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr = new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(buffer),
+ vertexCount, vertexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -63,9 +63,9 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
- std::move(iBuf), indexCount, indexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr =
+ new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(vBuf), vertexCount,
+ vertexOffset, std::move(iBuf), indexCount, indexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -133,7 +133,6 @@ static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring
ScopedUtfChars name(env, uniformName);
const float values[4] = {value1, value2, value3, value4};
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
- wrapper->markDirty();
}
static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
@@ -143,7 +142,6 @@ static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, js
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length(), isColor);
- wrapper->markDirty();
}
static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -166,7 +164,6 @@ static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring u
ScopedUtfChars name(env, uniformName);
const int values[4] = {value1, value2, value3, value4};
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
- wrapper->markDirty();
}
static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
@@ -176,7 +173,6 @@ static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstr
AutoJavaIntArray autoValues(env, values, 0);
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length());
- wrapper->markDirty();
}
static void MeshWrapper_destroy(Mesh* wrapper) {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 5c8285a8e1e9..e0216b680064 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -111,8 +111,8 @@ bool SkiaDisplayList::prepareListAndChildren(
}
auto grContext = info.canvasContext.getGrContext();
- for (auto mesh : mMeshes) {
- mesh->updateSkMesh(grContext);
+ for (const auto& bufferData : mMeshBufferData) {
+ bufferData->updateBuffers(grContext);
}
#endif
@@ -181,7 +181,7 @@ void SkiaDisplayList::reset() {
mDisplayList.reset();
- mMeshes.clear();
+ mMeshBufferData.clear();
mMutableImages.clear();
mVectorDrawables.clear();
mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index b9dc1c49f09e..071a4e8caaff 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include <deque>
+#include <memory>
#include "Mesh.h"
#include "RecordingCanvas.h"
@@ -172,7 +173,7 @@ public:
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
- std::vector<const Mesh*> mMeshes;
+ std::vector<std::shared_ptr<const MeshBufferData>> mMeshBufferData;
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e917f9a66917..45bfe1c4957f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -342,7 +342,7 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma
}
void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
- mDisplayList->mMeshes.push_back(&mesh);
+ mDisplayList->mMeshBufferData.push_back(mesh.refBufferData());
mRecorder.drawMesh(mesh, blender, paint);
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be389fe84..f33bcb7f9643 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@ flag {
name: "location_bypass"
namespace: "location"
description: "Enable location bypass appops behavior"
- bug: "301150056"
+ bug: "329151785"
}
flag {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6fa7616..bf3942559b8a 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@ flag {
name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "263520343"
+ bug: "185136506"
}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 752ebdf3d0e3..481268516b51 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -58,6 +58,7 @@ cc_library_shared {
"configuration.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
+ "input_transfer_token.cpp",
"looper.cpp",
"native_activity.cpp",
"native_window_jni.cpp",
diff --git a/native/android/input_transfer_token.cpp b/native/android/input_transfer_token.cpp
new file mode 100644
index 000000000000..501e1d312bfc
--- /dev/null
+++ b/native/android/input_transfer_token.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "InputTransferToken"
+
+#include <android/input_transfer_token_jni.h>
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <log/log_main.h>
+
+using namespace android;
+
+#define CHECK_NOT_NULL(name) \
+ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+void InputTransferToken_acquire(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->incStrong((void*)InputTransferToken_acquire);
+}
+
+void InputTransferToken_release(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->decStrong((void*)InputTransferToken_acquire);
+}
+
+AInputTransferToken* AInputTransferToken_fromJava(JNIEnv* env, jobject inputTransferTokenObj) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(inputTransferTokenObj);
+ InputTransferToken* inputTransferToken =
+ android_window_InputTransferToken_getNativeInputTransferToken(env,
+ inputTransferTokenObj);
+ CHECK_NOT_NULL(inputTransferToken);
+ InputTransferToken_acquire(inputTransferToken);
+ return reinterpret_cast<AInputTransferToken*>(inputTransferToken);
+}
+
+jobject AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+ const AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(aInputTransferToken);
+ const InputTransferToken* inputTransferToken =
+ reinterpret_cast<const InputTransferToken*>(aInputTransferToken);
+ return android_window_InputTransferToken_getJavaInputTransferToken(env, inputTransferToken);
+}
+
+void AInputTransferToken_release(AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(aInputTransferToken);
+ InputTransferToken* inputTransferToken =
+ reinterpret_cast<InputTransferToken*>(aInputTransferToken);
+ InputTransferToken_release(inputTransferToken);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35e37b2a6893..b2925bfa6881 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -98,6 +98,9 @@ LIBANDROID {
AInputQueue_getEvent;
AInputQueue_hasEvents;
AInputQueue_preDispatchEvent;
+ AInputTransferToken_fromJava; # introduced=35
+ AInputTransferToken_release; # introduced=35
+ AInputTransferToken_toJava; # introduced=35
AKeyEvent_getAction;
AKeyEvent_getDownTime;
AKeyEvent_getEventTime;
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 7dd16ba6c18e..7698e2b2d054 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -76,6 +76,9 @@ java_sdk_library {
"//apex_available:platform",
"com.android.nfcservices",
],
+ aconfig_declarations: [
+ "android.nfc.flags-aconfig",
+ ],
}
filegroup {
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
index daa02111f71f..8337fd2453df 100644
--- a/packages/CrashRecovery/OWNERS
+++ b/packages/CrashRecovery/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS \ No newline at end of file
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 5d71b7d98fdc..37b5d408a508 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.BackgroundThread;
-import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e7012e7e..d0fee44f791f 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@ import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Build;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -42,10 +41,11 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -707,7 +707,7 @@ public class RescueParty {
if (pm.getModuleInfo(packageName, 0) != null) {
return true;
}
- } catch (PackageManager.NameNotFoundException ignore) {
+ } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
}
return isPersistentSystemApp(packageName);
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 000000000000..fa4d6afc03d3
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+ public static final File[] EMPTY_FILE = new File[0];
+
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+
+ /** @hide */
+ public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+ return (val != null) ? val : EMPTY_FILE;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts from List of bytes to byte array
+ * @param list
+ * @return byte[]
+ */
+ public static byte[] toPrimitive(List<byte[]> list) {
+ if (list.size() == 0) {
+ return new byte[0];
+ }
+ int byteLen = list.get(0).length;
+ byte[] array = new byte[list.size() * byteLen];
+ for (int i = 0; i < list.size(); i++) {
+ for (int j = 0; j < list.get(i).length; j++) {
+ array[i * byteLen + j] = list.get(i)[j];
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ return appendInt(cur, val, false);
+ }
+
+ /**
+ * Adds value to given array.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+ boolean allowDuplicates) {
+ if (cur == null) {
+ return new int[] { val };
+ }
+ final int n = cur.length;
+ if (!allowDuplicates) {
+ for (int i = 0; i < n; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ }
+ int[] ret = new int[n + 1];
+ System.arraycopy(cur, 0, ret, 0, n);
+ ret[n] = val;
+ return ret;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
index a6ae68f62f10..afcf6895fd0d 100644
--- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 000000000000..e4923bfc4ecb
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+ /**
+ * Read a text file into a String, optionally limiting the length.
+ *
+ * @param file to read (will not seek, so things like /proc files are OK)
+ * @param max length (positive for head, negative of tail, 0 for no limit)
+ * @param ellipsis to add of the file was truncated (can be null)
+ * @return the contents of the file, possibly truncated
+ * @throws IOException if something goes wrong reading the file
+ * @hide
+ */
+ public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+ @Nullable String ellipsis) throws IOException {
+ InputStream input = new FileInputStream(file);
+ // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+ // input stream, bytes read not equal to buffer size is not necessarily the correct
+ // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+ BufferedInputStream bis = new BufferedInputStream(input);
+ try {
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
+ byte[] data = new byte[max + 1];
+ int length = bis.read(data);
+ if (length <= 0) return "";
+ if (length <= max) return new String(data, 0, length);
+ if (ellipsis == null) return new String(data, 0, max);
+ return new String(data, 0, max) + ellipsis;
+ } else if (max < 0) { // "tail" mode: keep the last N
+ int len;
+ boolean rolled = false;
+ byte[] last = null;
+ byte[] data = null;
+ do {
+ if (last != null) rolled = true;
+ byte[] tmp = last;
+ last = data;
+ data = tmp;
+ if (data == null) data = new byte[-max];
+ len = bis.read(data);
+ } while (len == data.length);
+
+ if (last == null && len <= 0) return "";
+ if (last == null) return new String(data, 0, len);
+ if (len > 0) {
+ rolled = true;
+ System.arraycopy(last, len, last, 0, last.length - len);
+ System.arraycopy(data, 0, last, last.length - len, len);
+ }
+ if (ellipsis == null || !rolled) return new String(last);
+ return ellipsis + new String(last);
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = bis.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+ } finally {
+ bis.close();
+ input.close();
+ }
+ }
+
+ /**
+ * Perform an fsync on the given FileOutputStream. The stream at this
+ * point must be flushed but not yet closed.
+ *
+ * @hide
+ */
+ public static boolean sync(FileOutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.getFD().sync();
+ }
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
+ /**
+ * List the files in the directory or return empty file.
+ *
+ * @hide
+ */
+ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+ : ArrayUtils.EMPTY_FILE;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
index 948ebcca0263..fdb15e2333d5 100644
--- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 000000000000..5cdc2536129a
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ private long[] newUnpaddedLongArray(int num) {
+ return new long[num];
+ }
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ /** @hide */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = growSize(mSize);
+ final long[] newArray = newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 000000000000..dbbef61f6777
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+ private static final String STRING_ARRAY_SEPARATOR = ":";
+
+ /** @hide */
+ public static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ // Do nothing
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+ + ", expected " + firstElementName);
+ }
+ }
+
+ /** @hide */
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
+
+ private static XmlPullParser newPullParser() {
+ try {
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** @hide */
+ public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+ throws IOException {
+ final byte[] magic = new byte[4];
+ if (in instanceof FileInputStream) {
+ try {
+ Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ }
+ in.mark(8);
+ in.read(magic);
+ in.reset();
+ }
+
+ final TypedXmlPullParser xml;
+ xml = (TypedXmlPullParser) newPullParser();
+ try {
+ xml.setInput(in, "UTF_8");
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return xml;
+ }
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d846a9..9cfdffd55267 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
@@ -18,34 +18,34 @@ package com.android.credentialmanager
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
/** The assets path to be used by all CredentialManager screenshot tests. */
private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager"
private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets"
-private const val ASSETS_PATH_ROBO =
- "${ASSETS_PREFIX}/tests/robotests/customization/assets"
+private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets"
private val isRobolectric = Build.FINGERPRINT.contains("robolectric")
-class CredentialManagerGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
-) : GoldenImagePathManager(
+class CredentialManagerGoldenPathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
+) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
+ InstrumentationRegistry.getInstrumentation()
.targetContext
.filesDir
.absolutePath
.toString() + "/credman_screenshots",
pathConfig = pathConfig,
-) {
+ ) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "CredentialManagerGoldenImagePathManager"
+ return "CredentialManagerGoldenPathManager"
}
-} \ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 28d83ee157b6..b8432137b35e 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -60,7 +60,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) {
@get:Rule
val screenshotRule = ComposeScreenshotTestRule(
emulationSpec,
- CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
)
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index e79176bdebcf..56b1c2e3d5a4 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -56,7 +56,7 @@ fun SinglePasskeyScreen(
headerContent = {
SignInHeader(
icon = entry.icon,
- title = stringResource(R.string.use_password_title),
+ title = stringResource(R.string.use_passkey_title),
)
},
accountContent = {
diff --git a/packages/PackageInstaller/res/values-watch/themes.xml b/packages/PackageInstaller/res/values-watch/themes.xml
index 5e52008c7fd6..814d08a54f15 100644
--- a/packages/PackageInstaller/res/values-watch/themes.xml
+++ b/packages/PackageInstaller/res/values-watch/themes.xml
@@ -16,5 +16,11 @@
-->
<resources>
- <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 000000000000..30cb9932f104
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+- Backup and restore: Datastore should support
+ [data backup](https://developer.android.com/guide/topics/data/backup) to
+ preserve user experiences on a new device.
+- Observer pattern: The
+ [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+ monitor data change in the datastore and
+ - trigger
+ [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+ automatically.
+ - track data change event to log metrics.
+ - update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+- performBackup: The data is updated incrementally but it is not well
+ documented. The `ParcelFileDescriptor` state parameters are normally ignored
+ and data is updated even there is no change.
+- restoreEntity: The implementation must take care not to seek or close the
+ underlying data source, nor read more than size() bytes from the stream when
+ restore (see
+ [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+ It is possible a `BackupHelper` prevents other `BackupHelper`s from
+ restoring data.
+- writeNewStateDescription: Existing implementations rarely notice that this
+ callback is invoked after all entities are restored, and check if necessary
+ data are all restored in `restoreEntity` (e.g.
+ [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+ which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+- The implementation only needs to focus on the `BackupRestoreEntity`
+ interface. The `InputStream` of restore will ensure bounded data are read,
+ and close the stream will be no-op.
+- The library computes checksum of the backup data automatically, so that
+ unchanged data will not be sent to Android backup system.
+- Data compression is supported:
+ - ZIP best compression is enabled by default, no extra effort needs to be
+ taken.
+ - It is safe to switch between compression and no compression in future,
+ the backup data will add 1 byte header to recognize the codec.
+ - To support other compression algorithms, simply wrap over the
+ `InputStream` and `OutputStream`. Actually, the checksum is computed in
+ this way by
+ [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+ and
+ [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+ see `BackupRestoreStorage` implementation for more details.
+- Enhanced forward compatibility for file is enabled: If a backup includes
+ data that didn't exist in earlier versions of the app, the data can still be
+ successfully restored in those older versions. This is achieved by extending
+ the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+ treat each file as an entity and do the backup / restore.
+- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+ do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+ // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+ @Volatile // backup/restore happens on Binder thread
+ var data: String? = null
+ private set
+
+ fun setData(data: String?) {
+ this.data = data
+ notifyChange(ChangeReason.UPDATE)
+ }
+
+ override val name: String
+ get() = "MyData"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ data = String(inputStream.readAllBytes(), UTF_8)
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+ }
+
+ override fun onRestoreFinished() {
+ // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+ // the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate();
+ BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+ }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+ override fun onCreate() {
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ }
+}
+```
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index 0e3949365646..cfdcaff4d34c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -26,23 +26,10 @@ import java.util.concurrent.ConcurrentHashMap
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
- private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
+ private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
private val executor = MoreExecutors.directExecutor()
- private val observer = Observer { reason -> notifyBackupManager(null, reason) }
-
- private val keyedObserver =
- KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
-
- private fun notifyBackupManager(key: Any?, reason: Int) {
- // prefer not triggering backup immediately after restore
- if (reason == ChangeReason.RESTORE) return
- // TODO: log storage name
- Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
- BackupManager.dataChanged(application.packageName)
- }
-
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
@@ -52,7 +39,8 @@ class BackupRestoreStorageManager private constructor(private val application: A
*/
fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
val fileStorages = mutableListOf<BackupRestoreFileStorage>()
- for ((keyPrefix, storage) in storages) {
+ for ((keyPrefix, storageWrapper) in storageWrappers) {
+ val storage = storageWrapper.storage
if (storage is BackupRestoreFileStorage) {
fileStorages.add(storage)
} else {
@@ -70,15 +58,8 @@ class BackupRestoreStorageManager private constructor(private val application: A
* The observers of the storages will be notified.
*/
fun onRestoreFinished() {
- for (storage in storages.values) {
- storage.notifyRestoreFinished()
- }
- }
-
- private fun BackupRestoreStorage.notifyRestoreFinished() {
- when (this) {
- is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
- is Observable -> notifyChange(ChangeReason.RESTORE)
+ for (storageWrapper in storageWrappers.values) {
+ storageWrapper.notifyRestoreFinished()
}
}
@@ -99,50 +80,82 @@ class BackupRestoreStorageManager private constructor(private val application: A
fun add(storage: BackupRestoreStorage) {
if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
val name = storage.name
- val oldStorage = storages.put(name, storage)
+ val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
if (oldStorage != null) {
throw IllegalStateException(
"Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
)
}
- storage.addObserver()
- }
-
- private fun BackupRestoreStorage.addObserver() {
- when (this) {
- is KeyedObservable<*> -> addObserver(keyedObserver, executor)
- is Observable -> addObserver(observer, executor)
- else ->
- throw IllegalArgumentException(
- "$this does not implement either KeyedObservable or Observable"
- )
- }
}
/** Removes all the storages. */
fun removeAll() {
- for ((name, _) in storages) remove(name)
+ for ((name, _) in storageWrappers) remove(name)
}
/** Removes storage with given name. */
fun remove(name: String): BackupRestoreStorage? {
- val storage = storages.remove(name)
- storage?.removeObserver()
- return storage
- }
-
- private fun BackupRestoreStorage.removeObserver() {
- when (this) {
- is KeyedObservable<*> -> removeObserver(keyedObserver)
- is Observable -> removeObserver(observer)
- }
+ val storageWrapper = storageWrappers.remove(name)
+ storageWrapper?.removeObserver()
+ return storageWrapper?.storage
}
/** Returns storage with given name. */
- fun get(name: String): BackupRestoreStorage? = storages[name]
+ fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
/** Returns storage with given name, exception is raised if not found. */
- fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
+ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+ private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+ Observer, KeyedObserver<Any?> {
+ init {
+ when (storage) {
+ is KeyedObservable<*> -> storage.addObserver(this, executor)
+ is Observable -> storage.addObserver(this, executor)
+ else ->
+ throw IllegalArgumentException(
+ "$this does not implement either KeyedObservable or Observable"
+ )
+ }
+ }
+
+ override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+ override fun onKeyChanged(key: Any?, reason: Int) {
+ notifyBackupManager(key, reason)
+ }
+
+ private fun notifyBackupManager(key: Any?, reason: Int) {
+ val name = storage.name
+ // prefer not triggering backup immediately after restore
+ if (reason == ChangeReason.RESTORE) {
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+ )
+ return
+ }
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+ )
+ BackupManager.dataChanged(application.packageName)
+ }
+
+ fun removeObserver() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.removeObserver(this)
+ is Observable -> storage.removeObserver(this)
+ }
+ }
+
+ fun notifyRestoreFinished() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+ is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+ }
+ }
+ }
companion object {
@Volatile private var instance: BackupRestoreStorageManager? = null
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc38ddb0..f47041df6ee3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -85,7 +85,7 @@ public class RestrictedLockUtils {
*/
@RequiresApi(Build.VERSION_CODES.M)
public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
+ final Intent intent = getShowAdminSupportDetailsIntent(admin);
int targetUserId = UserHandle.myUserId();
if (admin != null) {
if (admin.user != null
@@ -98,9 +98,16 @@ public class RestrictedLockUtils {
}
/**
- * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}.
*/
public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+ return getShowAdminSupportDetailsIntent(admin);
+ }
+
+ /**
+ * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ */
+ public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) {
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
if (admin != null) {
if (admin.component != null) {
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index bb518c0f1cc5..d156f95afa79 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index bb518c0f1cc5..d156f95afa79 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
index 10869f258b3b..fe877eeff117 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7fb3cc8..d59076082c66 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
@@ -17,28 +17,25 @@
package com.android.settingslib.spa.screenshot.util
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "SettingsGoldenPathManager"
}
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index ae85675ab1b8..16f6b5e773c9 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -44,7 +44,7 @@ class SettingsScreenshotTestRule(
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SettingsGoldenImagePathManager(
+ SettingsGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 3586dcb2909c..b21895b31256 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -35,12 +35,14 @@
android:id="@+id/dialog_with_icon_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/DialogWithIconTitle"/>
<TextView
android:id="@+id/dialog_with_icon_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/TextAppearanceSmall"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c896a3c4..1118efc3e953 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"BROADCAST_STATE_"},
value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
public @interface BroadcastState {}
+
private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
return;
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ private int getGroupId(CachedBluetoothDevice cachedDevice) {
+ int groupId = cachedDevice.getGroupId();
+ String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+ return groupId;
+ }
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ if (profile instanceof LeAudioProfile) {
+ Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+ return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+ }
+ }
+ Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c9364de32..b7108c98c3fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@ public class MobileMappings {
// Handle specific carrier config values for the default data SIM
int defaultDataSubId = SubscriptionManager.from(context)
.getDefaultDataSubscriptionId();
- PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+ PersistableBundle b = configMgr == null ? null
+ : configMgr.getConfigForSubId(defaultDataSubId);
if (b != null) {
config.alwaysShowDataRatIcon = b.getBoolean(
CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1c4112..69c7410818dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@ public class CreateUserDialogController {
.setMessage(messageResId)
.setNegativeButtonText(R.string.cancel)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
break;
case GRANT_ADMIN_DIALOG:
mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@ public class CreateUserDialogController {
.setMessage(R.string.user_grant_admin_message)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
if (mIsAdmin == null) {
mCustomDialogHelper.setButtonEnabled(false);
}
@@ -265,6 +267,7 @@ public class CreateUserDialogController {
.setTitle(R.string.user_info_settings_title)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.done);
+ mCustomDialogHelper.requestFocusOnTitle();
mEditUserInfoView.setVisibility(View.VISIBLE);
mGrantAdminView.setVisibility(View.GONE);
break;
@@ -273,7 +276,6 @@ public class CreateUserDialogController {
&& mEditUserPhotoController.getNewUserPhotoDrawable() != null)
? mEditUserPhotoController.getNewUserPhotoDrawable()
: mSavedDrawable;
-
String newName = mUserNameView.getText().toString().trim();
String defaultName = mActivity.getString(R.string.user_new_user_name);
mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3ddc606..4cf3bc23b9a0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -282,4 +283,13 @@ public class CustomDialogHelper {
}
return this;
}
+
+ /**
+ * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+ * is updated and needs to be read from the beginning.
+ */
+ public void requestFocusOnTitle() {
+ mDialogTitle.requestFocus();
+ mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615c8b7c..21cc9a85edbc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@ class AudioRepositoryImpl(
override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
withContext(backgroundCoroutineContext) {
- if (isMuted) {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
- } else {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
- }
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
}
private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 56b0bf74574f..c9ac97dcab7f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -23,8 +23,8 @@ import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@ class AudioVolumeInteractor(
streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
}
+ val ringerMode: StateFlow<RingerMode>
+ get() = audioRepository.ringerMode
+
suspend fun setVolume(audioStream: AudioStream, volume: Int) =
audioRepository.setVolume(audioStream, volume)
@@ -52,9 +55,14 @@ class AudioVolumeInteractor(
/** Checks if the volume can be changed via the UI. */
fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
- getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ combine(
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+ ) { isZenMuted, isRingMuted ->
+ !isZenMuted && !isRingMuted
+ }
} else {
- flowOf(true)
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
}
}
@@ -76,10 +84,10 @@ class AudioVolumeInteractor(
(audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
audioStreamModel.isMuted)
) {
- return 0
+ return audioStreamModel.minVolume
}
} else if (audioStreamModel.isMuted) {
- return 0
+ return audioStreamModel.minVolume
}
return audioStreamModel.volume
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a8022ce5..9860cd827fe7 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@ class AudioRepositoryTest {
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
- val streamType = it.arguments[1] as Int
- volumeByStream[it.arguments[0] as Int] = streamType
+ val streamType = it.arguments[0] as Int
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
val streamType = it.arguments[0] as Int
- isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
}
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index fe1529d11cd8..9c518de18582 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -192,7 +192,7 @@ public class VolumeControlProfileTest {
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
final Executor executor = (command -> new Thread(command).start());
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mProfile.registerCallback(executor, callback);
verify(mService).registerCallback(executor, callback);
@@ -200,7 +200,7 @@ public class VolumeControlProfileTest {
@Test
public void unregisterCallback_verifyIsCalled() {
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
mProfile.unregisterCallback(callback);
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 76bea3160afe..9ca575e30ea9 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -19,14 +19,4 @@
<resources>
<!-- Name of the activity for Settings storage. -->
<string name="app_label">Settings Storage</string>
-
- <!-- A notification is shown when the user's softap config has been changed due to underlying
- hardware restrictions. This is the notifications's title.
- [CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change">Hotspot settings have changed</string>
-
- <!-- A notification is shown when the user's softap config has been changed due to underlying
- hardware restrictions. This is the notification's summary message.
- [CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change_summary">Tap to see details</string>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 30d5d4b86a90..eaec617cfa70 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -269,6 +269,7 @@ public class SecureSettings {
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
- Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 893932f663b7..046d6e25ff31 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -424,5 +424,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 7b49608ceb7e..e5d62f8f9fac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1133,16 +1133,15 @@ public class SettingsBackupAgent extends BackupAgentHelper {
// Depending on device hardware, we may need to notify the user of a setting change
SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
- if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
- Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+ if (isConfigurationHasChanged(configInCloud, storedConfig)) {
+ Log.d(TAG, "restored ap configuration requires a conversion: "
+ ", configInCloud is " + configInCloud + " but storedConfig is "
+ storedConfig);
- WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
}
}
}
- private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+ private boolean isConfigurationHasChanged(SoftApConfiguration configInCloud,
SoftApConfiguration storedConfig) {
// Check if the cloud configuration was modified when restored to the device.
// All elements of the configuration are compared except:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
deleted file mode 100644
index dc51c4012021..000000000000
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.providers.settings;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.util.List;
-
-/**
- * Helper class for sending notifications when the user's Soft AP config was changed upon restore.
- */
-public class WifiSoftApConfigChangedNotifier {
- private WifiSoftApConfigChangedNotifier() {}
-
- /**
- * Send a notification informing the user that their' Soft AP Config was changed upon restore.
- * When the user taps on the notification, they are taken to the Wifi Tethering page in
- * Settings.
- */
- public static void notifyUserOfConfigConversion(Context context) {
- NotificationManager notificationManager =
- context.getSystemService(NotificationManager.class);
-
- // create channel, or update it if it already exists
- NotificationChannel channel = new NotificationChannel(
- SystemNotificationChannels.NETWORK_STATUS,
- context.getString(
- com.android.internal.R.string.notification_channel_network_status),
- NotificationManager.IMPORTANCE_LOW);
- notificationManager.createNotificationChannel(channel);
-
- notificationManager.notify(
- SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
- createConversionNotification(context));
- }
-
- private static Notification createConversionNotification(Context context) {
- Resources resources = context.getResources();
- CharSequence title = resources.getText(R.string.wifi_softap_config_change);
- CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
- int color = resources.getColor(
- android.R.color.system_notification_accent_color, context.getTheme());
-
- return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
- .setSmallIcon(R.drawable.ic_wifi_settings)
- .setPriority(Notification.PRIORITY_HIGH)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setContentTitle(title)
- .setContentText(contentSummary)
- .setContentIntent(getPendingActivity(context))
- .setTicker(title)
- .setShowWhen(false)
- .setLocalOnly(true)
- .setColor(color)
- .setStyle(new Notification.BigTextStyle()
- .setBigContentTitle(title)
- .setSummaryText(contentSummary))
- .setAutoCancel(true)
- .build();
- }
-
- private static PendingIntent getPendingActivity(Context context) {
- Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .setPackage(getSettingsPackageName(context));
- return PendingIntent.getActivity(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- }
-
- /**
- * @return Get settings package name.
- */
- private static String getSettingsPackageName(Context context) {
- if (context == null) return null;
-
- Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
- List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
- UserHandle.of(ActivityManager.getCurrentUser()));
- if (resolveInfos == null || resolveInfos.isEmpty()) {
- return "com.android.settings";
- }
- return resolveInfos.get(0).activityInfo.packageName;
- }
-}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 46c89900b54e..6eb2dd043c94 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@ public class SettingsBackupTest {
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eb2d13dc9eb5..43ea3ec3de4e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -698,6 +698,9 @@
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98591e9b76e9..3c18f17b8e96 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -179,6 +179,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
+ <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -910,7 +911,7 @@
<activity
android:name=".volume.panel.ui.activity.VolumePanelActivity"
- android:label="@string/sound_settings"
+ android:label="@string/accessibility_volume_settings"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleInstance"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index da06830357e8..e5e34695f40c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -115,10 +115,10 @@ flag {
}
flag {
- name: "notifications_background_media_icons"
+ name: "notifications_background_icons"
namespace: "systemui"
- description: "Updates icons for media notifications in the background."
- bug: "315143160"
+ description: "Moves part of the notification icon updates to the background."
+ bug: "315143361"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -463,6 +463,13 @@ flag {
}
flag {
+ name: "enable_contextual_tips_frequency_cap"
+ description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
+ namespace: "systemui"
+ bug: "322891421"
+}
+
+flag {
name: "enable_contextual_tips"
description: "Enables showing contextual tips."
namespace: "systemui"
@@ -617,3 +624,22 @@ flag {
description: "enables new focus outline for qs tiles when focused on with physical keyboard"
bug: "312899524"
}
+
+flag {
+ name: "edgeback_gesture_handler_get_running_tasks_background"
+ namespace: "systemui"
+ description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+ " class on the background thread."
+ bug: "325041960"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "qs_ui_refactor"
+ namespace: "systemui"
+ description: "Enables the new QS UI pipeline that follows recommended architecture and uses"
+ " Compose for the UI."
+ bug: "325099249"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e8e567..3f57f88a13d3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@ constructor(
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
ghostedView.setShouldBlockVisibilityChanges(false)
+ ghostedView.onActivityLaunchAnimationEnd()
} else {
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e70568b48..da6ccaa2dd2c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@ interface LaunchableView {
* @param block whether we should block/postpone all calls to `setVisibility`.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
+
+ /** Perform an action when the activity launch animation ends */
+ fun onActivityLaunchAnimationEnd() {}
}
/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index ef15c8461b95..f779cf3671d7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@ package com.android.compose
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -44,33 +47,45 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
-import com.android.compose.theme.LocalAndroidColorScheme
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
/**
* Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
*
* @param onValueChangeFinished is called when the slider settles on a [value]. This callback
* shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
* for this slider. You can create and pass in your own remembered instance to observe
* Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- * the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ * slider
+ * @param label is shown next to the icon.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlatformSlider(
value: Float,
@@ -86,7 +101,7 @@ fun PlatformSlider(
label: (@Composable (isDragging: Boolean) -> Unit)? = null,
) {
val sliderHeight: Dp = 64.dp
- val iconWidth: Dp = sliderHeight
+ val thumbSize: Dp = sliderHeight
var isDragging by remember { mutableStateOf(false) }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@ fun PlatformSlider(
}
}
}
- val paddingStart by
- animateDpAsState(
- targetValue =
- if ((!isDragging && value == valueRange.start) || icon == null) {
- 16.dp
- } else {
- 0.dp
- },
- label = "LabelIconSpacingAnimation"
- )
Box(modifier = modifier.height(sliderHeight)) {
Slider(
@@ -126,130 +131,277 @@ fun PlatformSlider(
sliderState = it,
enabled = enabled,
colors = colors,
- iconWidth = iconWidth,
draggingCornersRadius = draggingCornersRadius,
sliderHeight = sliderHeight,
+ thumbSize = thumbSize,
isDragging = isDragging,
- modifier = Modifier,
+ label = label,
+ icon = icon,
+ modifier = Modifier.fillMaxSize(),
)
},
- thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+ thumb = { Spacer(Modifier.size(thumbSize)) },
)
- if (icon != null || label != null) {
- Row(modifier = Modifier.fillMaxSize()) {
- icon?.let { iconComposable ->
- Box(
- modifier = Modifier.fillMaxHeight().aspectRatio(1f),
- contentAlignment = Alignment.Center,
- ) {
- iconComposable(isDragging)
- }
- }
-
- label?.let { labelComposable ->
- Box(
- modifier =
- Modifier.fillMaxHeight()
- .weight(1f)
- .padding(
- start = { paddingStart.roundToPx() },
- end = { sliderHeight.roundToPx() / 2 },
- ),
- contentAlignment = Alignment.CenterStart,
- ) {
- labelComposable(isDragging)
- }
- }
- }
+ if (enabled) {
+ Spacer(
+ Modifier.padding(8.dp)
+ .size(4.dp)
+ .align(Alignment.CenterEnd)
+ .background(color = colors.indicatorColor, shape = CircleShape)
+ )
}
}
}
+private enum class TrackComponent(val zIndex: Float) {
+ Background(0f),
+ Icon(1f),
+ Label(1f),
+}
+
@Composable
private fun Track(
sliderState: SliderState,
enabled: Boolean,
colors: PlatformSliderColors,
- iconWidth: Dp,
draggingCornersRadius: Dp,
sliderHeight: Dp,
+ thumbSize: Dp,
isDragging: Boolean,
+ icon: (@Composable (isDragging: Boolean) -> Unit)?,
+ label: (@Composable (isDragging: Boolean) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
- val iconWidthPx: Float
- val halfIconWidthPx: Float
- val targetIndicatorRadiusPx: Float
- val halfSliderHeightPx: Float
- with(LocalDensity.current) {
- halfSliderHeightPx = sliderHeight.toPx() / 2
- iconWidthPx = iconWidth.toPx()
- halfIconWidthPx = iconWidthPx / 2
- targetIndicatorRadiusPx =
- if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
- }
+ var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+ Layout(
+ modifier = modifier,
+ content = {
+ TrackBackground(
+ modifier = Modifier.layoutId(TrackComponent.Background),
+ drawingState = drawingState,
+ enabled = enabled,
+ colors = colors,
+ draggingCornersRadiusActive = draggingCornersRadius,
+ draggingCornersRadiusIdle = sliderHeight / 2,
+ isDragging = isDragging,
+ )
+ if (icon != null) {
+ Box(
+ modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ if (enabled) colors.iconColor else colors.disabledIconColor
+ ) {
+ icon(isDragging)
+ }
+ }
+ }
+ if (label != null) {
+ val offsetX by
+ animateFloatAsState(
+ targetValue =
+ if (enabled) {
+ if (drawingState.isLabelOnTopOfIndicator) {
+ drawingState.iconWidth.coerceAtLeast(
+ LocalDensity.current.run { 16.dp.toPx() }
+ )
+ } else {
+ val indicatorWidth =
+ drawingState.indicatorRight - drawingState.indicatorLeft
+ indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+ }
+ } else {
+ drawingState.iconWidth
+ },
+ label = "LabelIconSpacingAnimation"
+ )
+ Box(
+ modifier =
+ Modifier.layoutId(TrackComponent.Label)
+ .offset { IntOffset(offsetX.toInt(), 0) }
+ .padding(end = 16.dp),
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ colors.getLabelColor(
+ isEnabled = enabled,
+ isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+ )
+ ) {
+ label(isDragging)
+ }
+ }
+ }
+ },
+ measurePolicy =
+ TrackMeasurePolicy(
+ sliderState = sliderState,
+ thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+ isRtl = isRtl,
+ onDrawingStateMeasured = { drawingState = it }
+ )
+ )
+}
- val indicatorRadiusPx: Float by
- animateFloatAsState(
- targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+ drawingState: DrawingState,
+ enabled: Boolean,
+ colors: PlatformSliderColors,
+ draggingCornersRadiusActive: Dp,
+ draggingCornersRadiusIdle: Dp,
+ isDragging: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val indicatorRadiusDp: Dp by
+ animateDpAsState(
+ targetValue =
+ if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
label = "PlatformSliderCornersAnimation",
)
val trackColor = colors.getTrackColor(enabled)
val indicatorColor = colors.getIndicatorColor(enabled)
- val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
- val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
Canvas(modifier.fillMaxSize()) {
+ val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
val trackPath = Path()
trackPath.addRoundRect(
RoundRect(
- left = -halfIconWidthPx,
+ left = 0f,
top = 0f,
- right = size.width + halfIconWidthPx,
- bottom = size.height,
+ right = drawingState.totalWidth,
+ bottom = drawingState.totalHeight,
cornerRadius = trackCornerRadius,
)
)
drawPath(path = trackPath, color = trackColor)
+ val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
clipPath(trackPath) {
val indicatorPath = Path()
- if (isRtl) {
- indicatorPath.addRoundRect(
- RoundRect(
- left =
- size.width -
- size.width * sliderState.coercedNormalizedValue -
- halfIconWidthPx,
- top = 0f,
- right = size.width + iconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = indicatorCornerRadius,
- topRightCornerRadius = trackCornerRadius,
- bottomRightCornerRadius = trackCornerRadius,
- bottomLeftCornerRadius = indicatorCornerRadius,
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left = drawingState.indicatorLeft,
+ top = drawingState.indicatorTop,
+ right = drawingState.indicatorRight,
+ bottom = drawingState.indicatorBottom,
+ topLeftCornerRadius = trackCornerRadius,
+ topRightCornerRadius = indicatorCornerRadius,
+ bottomRightCornerRadius = indicatorCornerRadius,
+ bottomLeftCornerRadius = trackCornerRadius,
+ )
+ )
+ drawPath(path = indicatorPath, color = indicatorColor)
+ }
+ }
+}
+
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+ private val sliderState: SliderState,
+ private val thumbSize: Int,
+ private val isRtl: Boolean,
+ private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ // Slider adds a paddings to the Track to make spase for thumb
+ val desiredWidth = constraints.maxWidth + thumbSize
+ val desiredHeight = constraints.maxHeight
+ val backgroundPlaceable: Placeable =
+ measurables
+ .fastFirst { it.layoutId == TrackComponent.Background }
+ .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+ val iconPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+ ?.measure(
+ Constraints(
+ minWidth = desiredHeight,
+ maxWidth = desiredHeight,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
)
)
- } else {
- indicatorPath.addRoundRect(
- RoundRect(
- left = -halfIconWidthPx,
- top = 0f,
- right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = trackCornerRadius,
- topRightCornerRadius = indicatorCornerRadius,
- bottomRightCornerRadius = indicatorCornerRadius,
- bottomLeftCornerRadius = trackCornerRadius,
+
+ val iconSize = iconPlaceable?.width ?: 0
+ val labelMaxWidth = (desiredWidth - iconSize) / 2
+ val labelPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+ ?.measure(
+ Constraints(
+ minWidth = 0,
+ maxWidth = labelMaxWidth,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
)
)
+
+ val drawingState =
+ if (isRtl) {
+ DrawingState(
+ isRtl = true,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft =
+ (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+ indicatorTop = 0f,
+ indicatorRight = desiredWidth.toFloat(),
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ } else {
+ DrawingState(
+ isRtl = false,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft = 0f,
+ indicatorTop = 0f,
+ indicatorRight =
+ iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
}
- drawPath(path = indicatorPath, color = indicatorColor)
+
+ onDrawingStateMeasured(drawingState)
+
+ return layout(desiredWidth, desiredHeight) {
+ backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+ iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+ labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
}
}
}
+private data class DrawingState(
+ val isRtl: Boolean = false,
+ val totalWidth: Float = 0f,
+ val totalHeight: Float = 0f,
+ val indicatorLeft: Float = 0f,
+ val indicatorTop: Float = 0f,
+ val indicatorRight: Float = 0f,
+ val indicatorBottom: Float = 0f,
+ val iconWidth: Float = 0f,
+ val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+ get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
/** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
private val SliderState.coercedNormalizedValue: Float
get() {
@@ -268,17 +420,19 @@ private val SliderState.coercedNormalizedValue: Float
* @param trackColor fills the track of the slider. This is a "background" of the slider
* @param indicatorColor fills the slider from the start to the value
* @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
* @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
* @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
* @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
*/
data class PlatformSliderColors(
val trackColor: Color,
val indicatorColor: Color,
val iconColor: Color,
- val labelColor: Color,
+ val labelColorOnIndicator: Color,
+ val labelColorOnTrack: Color,
val disabledTrackColor: Color,
val disabledIndicatorColor: Color,
val disabledIconColor: Color,
@@ -300,10 +454,11 @@ object PlatformSliderDefaults {
@Composable
private fun lightThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.tertiaryContainer,
- indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ trackColor = colorResource(android.R.color.system_accent3_200),
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = MaterialTheme.colorScheme.onTertiaryContainer,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -314,10 +469,11 @@ private fun lightThemePlatformSliderColors() =
@Composable
private fun darkThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.onTertiary,
- indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ trackColor = colorResource(android.R.color.system_accent3_600),
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = colorResource(android.R.color.system_accent3_900),
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -329,3 +485,14 @@ private fun PlatformSliderColors.getTrackColor(isEnabled: Boolean): Color =
private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+ isEnabled: Boolean,
+ isLabelOnTopOfTheIndicator: Boolean
+): Color {
+ return if (isEnabled) {
+ if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+ } else {
+ disabledLabelColor
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a3372e3e83da..d0c498475d0b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -24,7 +24,6 @@ import com.android.compose.animation.scene.transitions
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
@@ -84,7 +83,7 @@ fun CommunalContainer(
SceneTransitionLayout(
state = sceneTransitionLayoutState,
- modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
+ modifier = modifier.fillMaxSize(),
swipeSourceDetector =
FixedSizeEdgeDetector(
dimensionResource(id = R.dimen.communal_gesture_initiation_width)
@@ -93,9 +92,14 @@ fun CommunalContainer(
scene(
CommunalScenes.Blank,
userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
- )
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+ CommunalScenes.Communal
+ )
+ } else {
+ emptyMap()
+ }
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
@@ -104,7 +108,13 @@ fun CommunalContainer(
scene(
CommunalScenes.Communal,
userActions =
- mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+ )
+ } else {
+ emptyMap()
+ },
) {
CommunalScene(viewModel, dialogFactory, modifier = modifier)
}
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 0d6b71066557..6a510bd13f7c 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
@@ -85,6 +85,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -177,33 +179,43 @@ fun CommunalHub(
}
.thenIf(!viewModel.isEditMode) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
+ }
+ val index =
+ adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we
+ // should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidgetContent() ||
+ communalContent[index] is
+ CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
}
- val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is CommunalContentModel.CtaTileInViewMode
- ) {
- isButtonToEditWidgetsShowing = true
+ val key =
+ index?.let { keyAtIndexIfEditable(communalContent, index) }
+ viewModel.setSelectedKey(key)
}
- val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
- viewModel.setSelectedKey(key)
}
- }
+ .onPreviewKeyEvent {
+ onKeyEvent(viewModel)
+ false
+ }
+ .motionEventSpy { onMotionEvent(viewModel) }
},
) {
CommunalHubLazyGrid(
@@ -311,6 +323,14 @@ fun CommunalHub(
}
}
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
@Composable
private fun ScrollOnNewSmartspaceEffect(
viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 96520b21cc72..7acb4d5498db 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,61 +19,31 @@ package com.android.systemui.keyguard.ui.composable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(
- viewModel.upDestinationSceneKey,
- viewModel.leftDestinationSceneKey,
- viewModel.downFromTopEdgeDestinationSceneKey,
- ) { upKey, leftKey, downFromTopEdgeKey ->
- destinationScenes(
- up = upKey,
- left = leftKey,
- downFromTopEdge = downFromTopEdgeKey,
- )
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- destinationScenes(
- up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey.value,
- downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
- )
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -84,22 +54,6 @@ constructor(
modifier = modifier,
)
}
-
- private fun destinationScenes(
- up: SceneKey?,
- left: SceneKey?,
- downFromTopEdge: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
- left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
- downFromTopEdge?.let {
- this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
- UserActionResult(downFromTopEdge)
- }
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
- }
- }
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5d9b014c5c96..eedff89e7a45 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -66,6 +66,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
+import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.background
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.colorAttr
@@ -77,16 +78,16 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@Composable
-fun FooterActionsWithAnimatedVisibility(
+fun SceneScope.FooterActionsWithAnimatedVisibility(
viewModel: FooterActionsViewModel,
isCustomizing: Boolean,
lifecycleOwner: LifecycleOwner,
- footerActionsModifier: (Modifier) -> Modifier,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
@@ -96,7 +97,7 @@ fun FooterActionsWithAnimatedVisibility(
FooterActions(
viewModel = viewModel,
qsVisibilityLifecycleOwner = lifecycleOwner,
- modifier = footerActionsModifier(Modifier),
+ modifier = Modifier.element(QuickSettings.Elements.FooterActions),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6ae1410623a3..5b9213a8f23c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -60,7 +60,6 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.res.R
@@ -249,9 +248,6 @@ private fun SceneScope.QuickSettingsScene(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
lifecycleOwner = lifecycleOwner,
- footerActionsModifier = { modifier ->
- modifier.element(QuickSettings.Elements.FooterActions)
- },
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
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 51464d059890..15e7b511915e 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
@@ -347,9 +347,6 @@ private fun SceneScope.SplitShade(
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
lifecycleOwner = lifecycleOwner,
- footerActionsModifier = { modifier ->
- modifier.element(QuickSettings.Elements.FooterActions)
- },
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
@@ -373,7 +370,6 @@ private fun SceneScope.MediaIfVisible(
) {
if (viewModel.isMediaVisible()) {
val density = LocalDensity.current
- val layoutWidth = remember { mutableStateOf(0) }
val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
MediaCarousel(
@@ -389,7 +385,7 @@ private fun SceneScope.MediaIfVisible(
layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
},
mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
+ layoutWidth = 0,
layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff819eb..b1fbe35eccd8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
import android.content.Context
+import android.view.ContextThemeWrapper
import android.view.View
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@ constructor(
AndroidView<SliceView>(
modifier = Modifier.fillMaxWidth(),
factory = { context: Context ->
- SliceView(context).apply {
- mode = SliceView.MODE_LARGE
- isScrollable = false
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- setShowTitleItems(true)
- addOnLayoutChangeListener(
- OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
- )
- }
+ SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+ .apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ addOnLayoutChangeListener(
+ OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+ )
+ }
},
update = { sliceView: SliceView -> sliceView.slice = slice }
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 5f7bd47f2a1b..b721e41eda27 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -32,6 +32,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
@@ -52,6 +57,7 @@ class ButtonComponent(
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
Column(
modifier = modifier,
@@ -59,7 +65,11 @@ class ButtonComponent(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expandable(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics {
+ role = Role.Button
+ contentDescription = label
+ },
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(28.dp),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -71,7 +81,8 @@ class ButtonComponent(
}
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index dfee684c417f..28fd785a09e5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,6 +32,9 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -50,13 +53,16 @@ class ToggleButtonComponent(
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
+
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OutlinedIconToggleButton(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
checked = viewModel.isChecked,
onCheckedChange = onCheckedChange,
shape = RoundedCornerShape(28.dp),
@@ -72,7 +78,8 @@ class ToggleButtonComponent(
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 53de5bc3a36d..6f2ed8178801 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -49,10 +49,16 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel
@@ -74,14 +80,19 @@ constructor(
viewModel.connectedDeviceViewModel.collectAsState()
val deviceIconViewModel: DeviceIconViewModel? by
viewModel.deviceIconViewModel.collectAsState()
+ val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
Expandable(
- modifier = Modifier.fillMaxWidth().height(80.dp),
+ modifier =
+ Modifier.fillMaxWidth().height(80.dp).semantics {
+ liveRegion = LiveRegionMode.Polite
+ this.onClick(label = clickLabel) { false }
+ },
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(28.dp),
onClick = { viewModel.onBarClick(it) },
) { _ ->
- Row(verticalAlignment = Alignment.CenterVertically) {
+ Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 89251939f77a..26086d1a9d0a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -24,13 +24,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
@@ -101,16 +104,19 @@ constructor(
}
}
- PlatformIconButton(
+ IconButton(
modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
- iconResource = R.drawable.ic_close,
- contentDescription = null,
onClick = { dialog.dismiss() },
colors =
IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.outline
)
- )
+ ) {
+ Icon(
+ painterResource(R.drawable.ic_close),
+ contentDescription = stringResource(R.string.accessibility_desc_close),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 4d810dfce89d..e1cf3a5f373b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -42,12 +42,14 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSliderColors
import com.android.systemui.res.R
@@ -61,12 +63,13 @@ private const val COLLAPSE_DURATION_MILLIS = 300
@Composable
fun ColumnVolumeSliders(
viewModels: List<SliderViewModel>,
+ isExpanded: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
sliderColors: PlatformSliderColors,
isExpandable: Boolean,
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Row(
@@ -78,16 +81,26 @@ fun ColumnVolumeSliders(
VolumeSlider(
modifier = Modifier.weight(1f),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
+ val expandButtonStateDescription =
+ if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
+ else stringResource(R.string.volume_panel_collapsed_sliders)
if (isExpandable) {
ExpandButton(
+ modifier =
+ Modifier.semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
isExpanded = isExpanded,
- onExpandedChanged = { isExpanded = it },
- sliderColors,
- Modifier,
+ onExpandedChanged = onExpandedChanged,
+ sliderColors = sliderColors,
)
}
}
@@ -116,9 +129,10 @@ fun ColumnVolumeSliders(
VolumeSlider(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
state = sliderState,
- onValueChangeFinished = {
- sliderViewModel.onValueChangeFinished(sliderState, it)
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee7285bdb..b284c691ef0e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@ fun GridVolumeSliders(
VolumeSlider(
modifier = Modifier.fillMaxWidth(),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 18a62dca3769..fa94ea01d533 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -18,18 +18,28 @@ package com.android.systemui.volume.panel.component.volume.ui.composable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.setProgress
+import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.ui.compose.Icon
@@ -38,23 +48,61 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
@Composable
fun VolumeSlider(
state: SliderState,
- onValueChangeFinished: (Float) -> Unit,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
) {
- var value by remember(state.value) { mutableFloatStateOf(state.value) }
+ val value by
+ animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
PlatformSlider(
- modifier = modifier,
+ modifier =
+ modifier.clearAndSetSemantics {
+ if (!state.isEnabled) disabled()
+ contentDescription = state.label
+
+ // provide a not animated value to the a11y because it fails to announce the settled
+ // value when it changes rapidly.
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
value = value,
valueRange = state.valueRange,
- onValueChange = { value = it },
- onValueChangeFinished = { onValueChangeFinished(value) },
+ onValueChange = onValueChange,
enabled = state.isEnabled,
icon = { isDragging ->
if (isDragging) {
- Text(text = value.toInt().toString())
+ Text(text = value.toInt().toString(), color = LocalContentColor.current)
} else {
- state.icon?.let { Icon(icon = it) }
+ state.icon?.let {
+ IconButton(
+ onClick = onIconTapped,
+ colors =
+ IconButtonColors(
+ contentColor = LocalContentColor.current,
+ containerColor = Color.Transparent,
+ disabledContentColor = LocalContentColor.current,
+ disabledContainerColor = Color.Transparent,
+ )
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = it)
+ }
+ }
}
},
colors = sliderColors,
@@ -64,19 +112,21 @@ fun VolumeSlider(
modifier = Modifier.basicMarquee(),
text = state.label,
style = MaterialTheme.typography.titleMedium,
+ color = LocalContentColor.current,
maxLines = 1,
)
state.disabledMessage?.let { message ->
AnimatedVisibility(
!state.isEnabled,
- enter = expandVertically { it },
- exit = shrinkVertically { it },
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
) {
Text(
modifier = Modifier.basicMarquee(),
text = message,
style = MaterialTheme.typography.bodySmall,
+ color = LocalContentColor.current,
maxLines = 1,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18deeaac2..fdf8ee872019 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@ constructor(
modifier = modifier.fillMaxWidth(),
)
} else {
+ val isExpanded by viewModel.isExpanded.collectAsState()
ColumnVolumeSliders(
viewModels = sliderViewModels,
+ isExpanded = isExpanded,
+ onExpandedChanged = viewModel::onExpandedChanged,
sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
isExpandable = isPortrait,
modifier = modifier.fillMaxWidth(),
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 6114499a2f5e..63ec54fbef9c 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
@@ -31,6 +31,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -353,10 +354,7 @@ private class DragControllerImpl(
// If the swipe was not committed or if the swipe distance is not computed yet, don't do
// anything.
- if (
- swipeTransition._currentScene != toScene ||
- distance == SwipeTransition.DistanceUnspecified
- ) {
+ if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
return fromScene to 0f
}
@@ -418,7 +416,7 @@ private class DragControllerImpl(
var targetScene: Scene
var targetOffset: Float
if (
- distance != SwipeTransition.DistanceUnspecified &&
+ distance != DistanceUnspecified &&
shouldCommitSwipe(
offset,
distance,
@@ -444,8 +442,8 @@ private class DragControllerImpl(
if (targetScene == fromScene) {
0f
} else {
- check(distance != SwipeTransition.DistanceUnspecified) {
- "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+ check(distance != DistanceUnspecified) {
+ "distance is equal to $DistanceUnspecified"
}
distance
}
@@ -628,6 +626,12 @@ private class SwipeTransition(
/** The spec to use when animating this transition to either [fromScene] or [toScene]. */
lateinit var swipeSpec: SpringSpec<Float>
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance: Float
+ get() = distance().absoluteValue
+ }
+
private var lastDistance = DistanceUnspecified
/** Whether [TransitionState.Transition.finish] was called on this transition. */
@@ -753,10 +757,6 @@ private class SwipeTransition(
/** The job in which [animatable] is animated. */
val job: Job,
)
-
- companion object {
- const val DistanceUnspecified = 0f
- }
}
private object DefaultSwipeDistance : UserActionDistance {
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 86124df295b4..e6f5d585e915 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
@@ -236,19 +236,28 @@ sealed interface TransitionState {
interface HasOverscrollProperties {
/**
- * The position of the [TransitionState.Transition.toScene].
+ * The position of the [Transition.toScene].
*
* Used to understand the direction of the overscroll.
*/
val isUpOrLeft: Boolean
/**
- * The relative orientation between [TransitionState.Transition.fromScene] and
- * [TransitionState.Transition.toScene].
+ * The relative orientation between [Transition.fromScene] and [Transition.toScene].
*
* Used to understand the orientation of the overscroll.
*/
val orientation: Orientation
+
+ /**
+ * Scope which can be used in the Overscroll DSL to define a transformation based on the
+ * distance between [Transition.fromScene] and [Transition.toScene].
+ */
+ val overscrollScope: OverscrollScope
+
+ companion object {
+ const val DistanceUnspecified = 0f
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd329a2..b46614397ff4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@ import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@ internal constructor(
overscrollSpecs.fastForEach { spec ->
if (spec.orientation == orientation && filter(spec)) {
if (match != null) {
- error("Found multiple transition specs for transition $scene")
+ error("Found multiple overscroll specs for overscroll $scene")
}
match = spec
}
@@ -297,6 +298,7 @@ internal class TransformationSpecImpl(
) {
when (current) {
is Translate,
+ is OverscrollTranslate,
is EdgeTranslate,
is AnchoredTranslate -> {
throwIfNotNull(offset, element, name = "offset")
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 bc52a28279dc..2c109a337f65 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
@@ -22,6 +22,7 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@ interface SceneTransitionsBuilder {
): OverscrollSpec
}
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
* [UserAction].
@@ -120,7 +120,7 @@ interface OverscrollBuilder : PropertyTransformationBuilder {
}
@TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
/**
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@ interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+ /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+ fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float = { 0f },
+ y: OverscrollScope.() -> Float = { 0f },
+ )
+}
+
+interface OverscrollScope {
+ /**
+ * Return the absolute distance between fromScene and toScene, if available, otherwise
+ * [DistanceUnspecified].
+ */
+ val absoluteDistance: Float
+}
+
/**
* An interface to decide where we should draw shared Elements or compose MovableElements.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5cc341..1c9080fa085d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@ import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
}
}
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
val transformations = mutableListOf<Transformation>()
private var range: TransformationRange? = null
protected var reversed = false
@@ -130,7 +131,7 @@ internal open class OverscrollBuilderImpl : OverscrollBuilder {
range = null
}
- private fun transformation(transformation: PropertyTransformation<*>) {
+ protected fun transformation(transformation: PropertyTransformation<*>) {
val transformation =
if (range != null) {
RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@ internal open class OverscrollBuilderImpl : OverscrollBuilder {
}
}
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
override var swipeSpec: SpringSpec<Float>? = null
override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@ internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilde
fractionRange(start, end, builder)
}
}
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+ override fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float,
+ y: OverscrollScope.() -> Float
+ ) {
+ transformation(OverscrollTranslate(matcher, x, y))
+ }
+}
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 04d5914bff69..849c9d71ec2f 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
@@ -21,11 +21,11 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
-/** Translate an element by a fixed amount of density-independent pixels. */
internal class Translate(
override val matcher: ElementMatcher,
private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@ internal class Translate(
}
}
}
+
+internal class OverscrollTranslate(
+ override val matcher: ElementMatcher,
+ val x: OverscrollScope.() -> Float = { 0f },
+ val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+ override fun transform(
+ layoutImpl: SceneTransitionLayoutImpl,
+ scene: Scene,
+ element: Element,
+ sceneState: Element.SceneState,
+ transition: TransitionState.Transition,
+ value: Offset,
+ ): Offset {
+ // 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
+
+ return Offset(
+ x = value.x + overscrollProperties.overscrollScope.x(),
+ y = value.y + overscrollProperties.overscrollScope.y(),
+ )
+ }
+}
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 059a10e23207..26e01fefcb9b 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
@@ -539,24 +539,20 @@ class ElementTest {
}
}
- @Test
- fun elementTransitionDuringOverscroll() {
+ private fun setupOverscrollScenario(
+ layoutWidth: Dp,
+ layoutHeight: Dp,
+ sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+ firstScroll: Float
+ ): MutableSceneTransitionLayoutStateImpl {
// 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 =
MutableSceneTransitionLayoutState(
initialScene = TestScenes.SceneA,
- transitions =
- transitions {
- overscroll(TestScenes.SceneB, Orientation.Vertical) {
- translate(TestElements.Foo, y = overscrollTranslateY)
- }
- }
+ transitions = transitions(sceneTransitions),
)
as MutableSceneTransitionLayoutStateImpl
@@ -585,9 +581,30 @@ class ElementTest {
rule.onRoot().performTouchInput {
val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
down(middleTop)
- // Scroll 50%
- moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ val firstScrollHeight = layoutHeight.toPx() * firstScroll
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
}
+ return state
+ }
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val overscrollTranslateY = 10.dp
+
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by overscrollTranslateY
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ },
+ firstScroll = 0.5f, // Scroll 50%
+ )
val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@ class ElementTest {
assertThat(state.currentOverscrollSpec).isNotNull()
fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
}
+
+ @Test
+ fun elementTransitionWithDistanceDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by layoutHeight
+ translate(TestElements.Foo, y = { absoluteDistance })
+ }
+ },
+ firstScroll = 1f, // 100% scroll
+ )
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 50%
+ moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+
+ // Scroll 150% (100% scroll + 50% overscroll)
+ assertThat(transition!!.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 250% (100% scroll + 150% overscroll)
+ assertThat(transition.progress).isEqualTo(2.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3eccdedfc..825fe138c3c4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -228,12 +228,14 @@ class TransitionDslTest {
@Test
fun overscrollSpec() {
val transitions = transitions {
- overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+ overscroll(TestScenes.SceneA, Orientation.Vertical) {
+ translate(TestElements.Bar, x = { 1f }, y = { 2f })
+ }
}
val overscrollSpec = transitions.overscrollSpecs.single()
val transformation = overscrollSpec.transformationSpec.transformations.single()
- assertThat(transformation).isInstanceOf(Translate::class.java)
+ assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 153d2b8769b3..73a66c629024 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -38,6 +38,10 @@ fun transition(
override val isUserInputOngoing: Boolean = isUserInputOngoing
override val isUpOrLeft: Boolean = isUpOrLeft
override val orientation: Orientation = orientation
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance = 0f
+ }
override fun finish(): Job {
error("finish() is not supported in test transitions")
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 000000000000..5eafbfc1f0b1
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- View ids for elements in large weather clock -->
+ <item type="id" name="weather_clock_time" />
+ <item type="id" name="weather_clock_date" />
+ <item type="id" name="weather_clock_weather_icon" />
+ <item type="id" name="weather_clock_temperature" />
+ <item type="id" name="weather_clock_alarm_dnd" />
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index adf4fc6c8ae3..b253309104d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -140,6 +140,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
}
@Test
+ fun canShowAlternateBouncerForFingerprint_primaryBouncerShowing() {
+ givenCanShowAlternateBouncer()
+ bouncerRepository.setPrimaryShow(true)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
fun show_whenCannotShow() {
givenCannotShowAlternateBouncer()
@@ -202,7 +210,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
} else {
bouncerRepository.setAlternateBouncerUIAvailable(true)
}
-
+ bouncerRepository.setPrimaryShow(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 09fdd11a99dd..bd1403a6aa26 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -201,6 +202,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPin() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -208,8 +210,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
whenever(telephonyManager.supplyIccLockPin(anyString()))
.thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
verify(keyguardUpdateMonitor).reportSimUnlocked(1)
@@ -218,6 +219,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPin_incorrect_oneRemainingAttempt() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -229,9 +231,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
1,
)
)
-
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
@@ -245,6 +245,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPin_incorrect_threeRemainingAttempts() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -257,8 +258,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
)
)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
}
@@ -266,10 +266,11 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPin_notCorrectLength_tooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0))
+ verifySim(listOf(0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -277,10 +278,12 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPin_notCorrectLength_tooLong() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -288,6 +291,7 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPuk() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager)
whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
@@ -295,13 +299,13 @@ class SimBouncerInteractorTest : SysuiTestCase() {
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
runCurrent()
@@ -311,37 +315,49 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Test
fun verifySimPuk_inputTooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
@Test
fun verifySimPuk_pinNotCorrectLength() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+
+ verifySim(listOf(0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@Test
fun verifySimPuk_confirmedPinDoesNotMatch() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+ verifySim(listOf(0, 0, 0, 1))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
}
+ private suspend fun TestScope.verifySim(pinDigits: List<Int>) {
+ runCurrent()
+ underTest.verifySim(pinDigits)
+ }
+
@Test
fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
bouncerSimRepository.setSimVerificationErrorMessage("abc")
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 ce6445b75fb9..43266bfcbc55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
+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.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@ class CommunalSceneStartableTest : SysuiTestCase() {
@Before
fun setUp() {
with(kosmos) {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
underTest =
CommunalSceneStartable(
dockManager = dockManager,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ systemSettings = fakeSettings,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
)
@@ -246,6 +255,132 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
}
+ @Test
+ fun hubTimeout_whenDreaming_goesToBlank() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_notDreaming_staysOnCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is not dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(false)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ // Scene stays as Communal
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStopped_staysOnCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is on communal, but not dreaming.
+ fakeKeyguardRepository.setDreaming(false)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout, then start dreaming.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ fakeKeyguardRepository.setDreaming(true)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Device times out after one screen timeout interval, dream doesn't reset timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_userActivityTriggered_resetsTimeout() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
+
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_screenTimeoutChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
@@ -260,4 +395,8 @@ class CommunalSceneStartableTest : SysuiTestCase() {
setCommunalAvailable(true)
runCurrent()
}
+
+ companion object {
+ private const val SCREEN_TIMEOUT = 1000
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
index 2e9ee5ca2851..4a7757ba1820 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +36,7 @@ class DeviceEntryBiometricSettingsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+ private val testScope = kosmos.testScope
@Test
fun isCoex_true() = runTest {
@@ -59,4 +61,25 @@ class DeviceEntryBiometricSettingsInteractorTest : SysuiTestCase() {
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
assertThat(isCoex).isFalse()
}
+
+ @Test
+ fun authenticationFlags_providesAuthFlagsFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticationFlags)
+ .isSameInstanceAs(biometricSettingsRepository.authenticationFlags)
+ }
+
+ @Test
+ fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFaceAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled)
+ }
+
+ @Test
+ fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFingerprintAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 4f44705b7e72..70ceb2a75d7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -27,8 +28,21 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -36,6 +50,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -230,8 +245,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
assertThat(canSwipeToEnter).isFalse()
trustRepository.setCurrentUserTrusted(true)
- runCurrent()
faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
assertThat(canSwipeToEnter).isTrue()
}
@@ -383,6 +398,204 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
assertThat(isUnlocked).isTrue()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 87b1bbb9ff70..1adf414c9ef0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -40,7 +40,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import java.util.Optional
@@ -50,7 +49,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -136,12 +134,17 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_HOME_PANEL_DREAM)
- fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
testScope.runTest {
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
startable.start()
runCurrent()
- verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 000000000000..8f03717b42f2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * 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.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+ @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var testView: View
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
+ private val effectDuration = 400
+ private val lowTickDuration = 12
+ private val spinDuration = 133
+
+ private lateinit var longPressEffect: QSLongPressEffect
+
+ @Before
+ fun setup() {
+ whenever(
+ vibratorHelper.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
+ )
+ )
+ .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ effectDuration,
+ )
+ }
+
+ @Test
+ fun onActionDown_whileIdle_startsWait() = testWithScope {
+ // GIVEN an action down event occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
+ fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+ // GIVEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect goes back to idle and does not start
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+ // GIVEN an action is being collected
+ val action by collectLastValue(longPressEffect.actionType)
+
+ // GIVEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the action to invoke is the click action and the effect does not start
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+ // GIVEN the pressed timeout is complete
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN the effect starts
+ assertEffectStarted()
+ }
+
+ @Test
+ fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onAnimationComplete_effectEnds() = testWhileRunning {
+ // GIVEN that the animation completes
+ animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+ // THEN the long-press effect completes
+ assertEffectCompleted()
+ }
+
+ @Test
+ fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN an action down occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect resets
+ assertEffectResets()
+ }
+
+ @Test
+ fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN that the animation completes after a sufficient amount of time
+ animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+ // THEN the state goes to [QSLongPressEffect.State.IDLE]
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ private fun buildMotionEvent(action: Int): MotionEvent =
+ MotionEventBuilder.newBuilder().setAction(action).build()
+
+ private fun testWithScope(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the TIMEOUT_WAIT state is entered
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the down event that enters the TIMEOUT_WAIT state
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // GIVEN that the timeout completes and the effect starts
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ /**
+ * Asserts that the effect started by checking that:
+ * 1. The effect progress is 0f
+ * 2. Initial hint haptics are played
+ * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectStarted() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ lowTickDuration,
+ spinDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(effectProgress).isEqualTo(0f)
+ assertThat(longPressHint).isNotNull()
+ verify(vibratorHelper).vibrate(longPressHint!!)
+ }
+
+ /**
+ * Asserts that the effect did not start by checking that:
+ * 1. No effect progress is emitted
+ * 2. No haptics are played
+ * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+ * [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectDidNotStart() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(effectProgress).isNull()
+ verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+ }
+
+ /**
+ * Asserts that the effect completes by checking that:
+ * 1. The progress is null
+ * 2. The final snap haptics are played
+ * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 4. The action to perform on the tile is the long-press action
+ */
+ private fun TestScope.assertEffectCompleted() {
+ val action by collectLastValue(longPressEffect.actionType)
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ assertThat(effectProgress).isNull()
+ assertThat(snapEffect).isNotNull()
+ verify(vibratorHelper).vibrate(snapEffect!!)
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+ }
+
+ /**
+ * Assert that the effect gets reverted by checking that:
+ * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+ * 2. The reverse haptics plays at the point where the animation was paused
+ */
+ private fun assertEffectReverses(pausedProgress: Float) {
+ val reverseHaptics =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ lowTickDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(reverseHaptics).isNotNull()
+ verify(vibratorHelper).vibrate(reverseHaptics!!)
+ }
+
+ /**
+ * Asserts that the effect resets by checking that:
+ * 1. The effect progress resets to 0
+ * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+ */
+ private fun TestScope.assertEffectResets() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ assertThat(effectProgress).isEqualTo(0f)
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index cd4db2fbf55f..769caaa8454f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -47,9 +47,7 @@ import org.junit.runner.RunWith
@kotlinx.coroutines.ExperimentalCoroutinesApi
@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
-
val kosmos = testKosmos()
-
val underTest = kosmos.keyguardTransitionInteractor
val repository = kosmos.fakeKeyguardTransitionRepository
val testScope = kosmos.testScope
@@ -242,33 +240,34 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
}
@Test
- fun transitionValue() = runTest {
- val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
+ fun transitionValue() =
+ testScope.runTest {
+ val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
- val toSteps =
- listOf(
- TransitionStep(AOD, DOZING, 0f, STARTED),
- TransitionStep(AOD, DOZING, 0.5f, RUNNING),
- TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
- toSteps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
+ val toSteps =
+ listOf(
+ TransitionStep(AOD, DOZING, 0f, STARTED),
+ TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+ TransitionStep(AOD, DOZING, 1f, FINISHED),
+ )
+ toSteps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
- val fromSteps =
- listOf(
- TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
- TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
- TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
- )
- fromSteps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
+ val fromSteps =
+ listOf(
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
+ )
+ fromSteps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
- assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
- }
+ assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
+ }
@Test
fun isInTransitionToAnyState() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 64125f139a2e..af9678022e07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -99,6 +99,31 @@ class GlanceableHubToLockscreenTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
}
+ @Test
+ fun lockscreenTranslationX_resetsAfterCancellation() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+ 100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0.3f),
+ step(0.5f),
+ step(0.9f, TransitionState.CANCELED)
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ assertThat(values.last().value).isEqualTo(0f)
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 979d50463a04..869b2bfd7752 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -250,6 +250,17 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
keyguardRepository.topClippingBounds.value = 1000
assertThat(topClippingBounds).isEqualTo(1000)
+
+ // Run at least 1 transition to make sure value remains at 0
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ // Make sure the value hasn't changed since we're GONE
+ keyguardRepository.topClippingBounds.value = 5
+ assertThat(topClippingBounds).isEqualTo(1000)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 19950a5fb89d..2fd2ef1f3240 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -31,86 +34,129 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+ " isSingleShade={3}, isCommunalAvailable={4}"
+ )
+ @JvmStatic
+ fun combinations() = buildList {
+ repeat(32) { combination ->
+ add(
+ arrayOf(
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ )
+ )
+ }
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun setUp() {
+ val combinationStrings =
+ combinations().map { array ->
+ check(array.size == 5)
+ "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ }
+ val uniqueCombinations = combinationStrings.toSet()
+ assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+ }
+
+ private fun expectedDownDestination(
+ downFromEdge: Boolean,
+ isSingleShade: Boolean,
+ ): SceneKey {
+ return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ }
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+ @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+ @JvmField @Parameter(2) var downFromEdge: Boolean = false
+ @JvmField @Parameter(3) var isSingleShade: Boolean = true
+ @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
- fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun destinationScenes() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
+ if (canSwipeToEnter) {
+ AuthenticationMethodModel.None
+ } else {
+ AuthenticationMethodModel.Pin
+ }
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ kosmos.shadeRepository.setShadeMode(
+ if (isSingleShade) {
+ ShadeMode.Single
+ } else {
+ ShadeMode.Split
+ }
+ )
+ kosmos.setCommunalAvailable(isCommunalAvailable)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun leftTransitionSceneKey_communalIsAvailable_communal() =
- testScope.runTest {
- val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
- assertThat(leftDestinationSceneKey).isNull()
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- kosmos.setCommunalAvailable(true)
- runCurrent()
- assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
- }
+ assertThat(
+ destinationScenes
+ ?.get(
+ Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top.takeIf { downFromEdge },
+ pointerCount = if (downWithTwoPointers) 2 else 1,
+ )
+ )
+ ?.toScene
+ )
+ .isEqualTo(
+ expectedDownDestination(
+ downFromEdge = downFromEdge,
+ isSingleShade = isSingleShade,
+ )
+ )
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isEqualTo(Scenes.QuickSettings)
- }
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isNull()
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+ .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c64312..c0e5a9bc9990 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -114,7 +114,7 @@ class DisabledByPolicyInteractorTest : SysuiTestCase() {
DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
)
- val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN)
assertThat(result).isTrue()
verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 42c33544416d..af9abcda73fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
@@ -317,8 +316,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -337,8 +336,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -356,7 +355,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -379,7 +378,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -447,8 +446,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -469,8 +468,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -487,8 +486,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -507,8 +506,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa3603d722..cd79ed1a8965 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -54,7 +54,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var underTest: ShadeControllerSceneImpl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index e683f34c4ea1..53a8e5dbda32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -38,6 +39,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
@@ -699,41 +701,57 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ fun alphaOnFullQsExpansion() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreenWithQSExpanded()
+
+ // Alpha fades out as QS expands
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isWithin(0.01f).of(0.5f)
+ shadeRepository.setQsExpansion(0.9f)
+ assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+ // Ensure that alpha is set back to 1f when QS is fully expanded
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun shadeCollapseFadeIn() =
testScope.runTest {
- val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+ val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
// Start on lockscreen without the shade
- underTest.setShadeCollapseFadeInComplete(false)
showLockscreen()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then the shade expands
showLockscreenWithShadeExpanded()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... it collapses
showLockscreen()
- assertThat(fadeIn).isEqualTo(true)
+ assertThat(fadeIn[1]).isEqualTo(true)
- // ... now send animation complete signal
- underTest.setShadeCollapseFadeInComplete(true)
- assertThat(fadeIn).isEqualTo(false)
+ // ... and ensure the value goes back to false
+ assertThat(fadeIn[2]).isEqualTo(false)
}
@Test
fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
testScope.runTest {
- val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+ val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
// Start on lockscreen without the shade
- underTest.setShadeCollapseFadeInComplete(false)
showLockscreen()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then the shade expands
showLockscreenWithShadeExpanded()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then user hits power to go to AOD
keyguardTransitionRepository.sendTransitionSteps(
@@ -744,7 +762,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
// ... followed by a shade collapse
showLockscreen()
// ... does not trigger a fade in
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
}
private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 000000000000..183a58a495a3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+ private val mAvalancheController = AvalancheController()
+
+ // For creating mocks
+ @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+ @Mock private val runnableMock: Runnable? = null
+
+ // For creating TestableHeadsUpManager
+ @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+ private val mUiEventLoggerFake = UiEventLoggerFake()
+ private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mGlobalSettings = FakeGlobalSettings()
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
+ private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+ @Before
+ fun setUp() {
+ // Use default non-a11y timeout
+ Mockito.`when`(
+ mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .then { i: InvocationOnMock -> i.getArgument(0) }
+
+ // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ testableHeadsUpManager =
+ TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ mAvalancheController
+ )
+ }
+
+ private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+ entry.setEntry(
+ NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+ .build()
+ )
+ return entry
+ }
+
+ @Test
+ fun testUpdate_isShowing_runsRunnable() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testUpdate_noneShowingAndNotNext_showNow() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is showing now
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testUpdate_isNext_addsRunnable() {
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Entry has one Runnable
+ val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+ Truth.assertThat(runnableList).isNotNull()
+ Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry has two Runnables
+ Truth.assertThat(runnableList.size).isEqualTo(2)
+ }
+
+ @Test
+ fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+ }
+
+ @Test
+ fun testDelete_isNext_removedFromNext_runnableNotRun() {
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry was removed from next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_wasDropped_removedFromDropSet() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry was removed from dropSet
+ Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+ }
+
+ @Test
+ fun testDelete_wasDropped_runnableNotRun() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_runnableRun() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_showNext() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f4c864..830bcef55046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+ private AvalancheController mAvalancheController = new AvalancheController();
+
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+ mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7cb06b2..61a79d897b0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
+ private AvalancheController mAvalancheController = new AvalancheController();
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController
) {
super(
context,
@@ -109,7 +111,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
accessibilityManagerWrapper,
uiEventLogger,
javaAdapter,
- shadeInteractor
+ shadeInteractor,
+ avalancheController
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
mAccessibilityManagerWrapper,
mUiEventLogger,
mJavaAdapter,
- mShadeInteractor
+ mShadeInteractor,
+ mAvalancheController
);
}
@Before
public void setUp() {
+ // TODO(b/315362456) create separate test with the flag disabled
+ // then modify this file to test with the flag enabled
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 27476299cf18..d8f77f054b49 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
+ executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a2f3ccb8c416..3d936545bbb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -113,7 +113,29 @@ class AudioVolumeInteractorTest : SysuiTestCase() {
}
@Test
- fun streamIsMuted_getStream_volumeZero() {
+ fun zenMuted_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun streamIsMuted_getStream_volumeMin() {
with(kosmos) {
testScope.runTest {
val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -144,7 +166,7 @@ class AudioVolumeInteractorTest : SysuiTestCase() {
}
@Test
- fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -162,7 +184,7 @@ class AudioVolumeInteractorTest : SysuiTestCase() {
}
@Test
- fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ fun ringerModeVibrate_getRingerStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 449e8bf6998f..1ed7f5d04622 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.panel.component.spatial.domain
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.testing.TestableLooper.RunWithLooper
@@ -49,26 +51,27 @@ import org.junit.runner.RunWith
class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
- whenever(address).thenReturn("test_address")
- }
- private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
- whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
- }
-
private lateinit var underTest: SpatialAudioAvailabilityCriteria
@Before
fun setup() {
with(kosmos) {
- mediaControllerRepository.setActiveLocalMediaController(
- mediaController.apply {
- whenever(packageName).thenReturn("test.pkg")
- whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+ val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+ whenever(address).thenReturn("test_address")
+ }
+ localMediaRepository.updateCurrentConnectedDevice(
+ mock<BluetoothMediaDevice> {
+ whenever(name).thenReturn("test_device")
+ whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
}
)
+ whenever(mediaController.packageName).thenReturn("test.pkg")
+ whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+ mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
}
}
@@ -77,9 +80,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
fun noSpatialAudio_noHeadTracking_unavailable() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = false
- spatializerRepository.defaultSpatialAudioAvailable = false
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -93,9 +95,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
fun spatialAudio_noHeadTracking_available() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = false
- spatializerRepository.defaultSpatialAudioAvailable = true
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -109,9 +110,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
fun spatialAudio_headTracking_available() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = true
- spatializerRepository.defaultSpatialAudioAvailable = true
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, true)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -125,8 +125,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
fun spatialAudio_headTracking_noDevice_unavailable() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.defaultHeadTrackingAvailable = true
- spatializerRepository.defaultSpatialAudioAvailable = true
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -135,4 +135,13 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
}
}
}
+
+ private companion object {
+ val headset =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address"
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220876d6..281b03d69536 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
@@ -37,6 +38,7 @@ import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,16 +76,6 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
mediaControllerRepository.setActiveLocalMediaController(mediaController)
- spatializerRepository.setIsSpatialAudioAvailable(
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- "test_address"
- ),
- true
- )
- spatializerRepository.defaultHeadTrackingAvailable = true
-
underTest =
SpatialAudioComponentInteractor(
mediaOutputInteractor,
@@ -97,6 +89,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
fun setEnabled_changesIsEnabled() {
with(kosmos) {
testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
val values by collectValues(underTest.isEnabled)
underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -108,6 +101,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
assertThat(values)
.containsExactly(
+ SpatialAudioEnabledModel.Unknown,
SpatialAudioEnabledModel.Disabled,
SpatialAudioEnabledModel.HeadTrackingEnabled,
SpatialAudioEnabledModel.SpatialAudioEnabled,
@@ -116,4 +110,92 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
}
}
}
+
+ @Test
+ fun connectedDeviceSupports_isAvailable_SpatialAudio() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+ }
+ }
+ }
+
+ private companion object {
+ val headset =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address"
+ )
+ val builtinSpeaker =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ ""
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fcafd3a4..79d3fe9063b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -30,28 +30,8 @@ class VolumeSliderInteractorTest : SysuiTestCase() {
private val underTest = VolumeSliderInteractor()
@Test
- fun translateValueToVolume() {
- assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
- }
-
- @Test
- fun processVolumeToValue_muted_zero() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
- }
-
- @Test
- fun processVolumeToValue_currentValue_currentValue() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ fun processVolumeToValue_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3957b6..82ce6d785e90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
DefaultComponentsLayoutManager(
BOTTOM_BAR,
headerComponents = listOf(COMPONENT_1),
- footerComponents = listOf(COMPONENT_2),
+ footerComponents = listOf(COMPONENT_5, COMPONENT_2),
)
@Test
@@ -48,10 +48,18 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+ val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
val layout =
underTest.layout(
VolumePanelState(0, false, false),
- setOf(bottomBarComponentState, component1, component2, component3, component4)
+ setOf(
+ bottomBarComponentState,
+ component1,
+ component2,
+ component3,
+ component4,
+ component5,
+ )
)
Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
.containsExactlyElementsIn(listOf(component1))
.inOrder()
Truth.assertThat(layout.footerComponents)
- .containsExactlyElementsIn(listOf(component2))
+ .containsExactlyElementsIn(listOf(component5, component2))
.inOrder()
Truth.assertThat(layout.contentComponents)
.containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+ const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
}
}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e1098f333..7341015e8690 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
<!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
+ <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+ <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+ <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 25596cce3b97..e3a5e156b055 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1536,8 +1536,12 @@
<!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
<string name="media_device_cast">Cast</string>
- <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+ <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
<string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+ <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+ <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
@@ -1552,11 +1556,11 @@
<string name="volume_panel_noise_control_title">Noise Control</string>
<!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
<string name="volume_panel_spatial_audio_title">Spatial Audio</string>
- <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is disabled [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_off">Off</string>
- <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled without head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_fixed">Fixed</string>
- <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled with head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
@@ -1572,6 +1576,18 @@
<string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
+ <!-- An audible a11y label for a button, that opens settings when clicked [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_enter_media_output_settings">Enter output settings</string>
+ <!-- An audible a11y state description for a button, that expands volume sliders menu [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_expanded_sliders">Volume sliders expanded</string>
+ <!-- An audible a11y state description for a button, that collapses volume sliders menu [CHAR LIMIT=NONE] -->
+ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
+
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_mute">mute %s</string>
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_unmute">unmute %s</string>
+
<!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
<string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e7809ade792..59516be65a5e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
+ <style name="Widget.SliceView.VolumePanel">
+ <item name="hideHeaderRow">true</item>
+ </style>
+
<style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
<item name="android:dialogCornerRadius">44dp</item>
<item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea708031..26e91b62d19a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_password;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_password;
+ return R.string.kg_prompt_added_security_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900da887a..caa74780538e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@ public class KeyguardPatternViewController
resId = R.string.kg_prompt_after_user_lockdown_pattern;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- resId = R.string.kg_prompt_reason_timeout_pattern;
+ resId = R.string.kg_prompt_added_security_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f054dd6..fbe9edfd6680 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_pin;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_pin;
+ return R.string.kg_prompt_added_security_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1f4036..7f9ae5e578e6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -250,6 +250,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
@Override
protected void onViewAttached() {
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
+
mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mConfigurationController.addCallback(mConfigurationListener);
@@ -257,6 +261,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
@Override
protected void onViewDetached() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
+
mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
mConfigurationController.removeCallback(mConfigurationListener);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d200d1fa..a90d4b2b6061 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@ class DragToInteractView(context: Context) : FrameLayout(context) {
iconResId = R.drawable.pip_ic_close_white
)
)
+
+ // Ensure this is unfocusable & uninteractable
+ isClickable = false
+ isFocusable = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
// END DragToInteractView modification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e092b3a..1f0459978c3c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@ class MenuAnimationController {
/* Moves position without updating underlying percentage position. Can be animated. */
void moveToPosition(PointF position, boolean animateMovement) {
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- moveToPositionX(position.x, animateMovement);
- moveToPositionY(position.y, animateMovement);
- } else {
- moveToPositionX(position.x, /* animateMovement = */ false);
- moveToPositionY(position.y, /* animateMovement = */ false);
- }
+ moveToPositionX(position.x, animateMovement);
+ moveToPositionY(position.y, animateMovement);
}
void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@ class MenuAnimationController {
}
void moveToPositionX(float positionX, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_X,
createSpringForce(),
/* velocity = */ 0,
@@ -142,7 +137,7 @@ class MenuAnimationController {
}
void moveToPositionY(float positionY, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_Y,
createSpringForce(),
/* velocity = */ 0,
@@ -455,7 +450,7 @@ class MenuAnimationController {
? MIN_PERCENT
: Math.min(MAX_PERCENT, position.y / draggableBounds.height());
- if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+ if (!writeToPosition) {
mMenuView.onEdgeChangedIfNeeded();
} else {
mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b81490..35fe6b14ee28 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@ class MenuMessageView extends LinearLayout implements
addView(mTextView, Index.TEXT_VIEW,
new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+ // The message box is not focusable, but will announce its contents when it appears.
+ // The textView and button are still interactable.
+ setClickable(false);
+ setFocusable(false);
+ setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 577bbc0bd840..0c9712de3264 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,17 +21,10 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.SettingsStringUtil;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -101,6 +94,10 @@ class MenuView extends FrameLayout implements
loadLayoutResources();
addView(mTargetFeaturesView);
+
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -224,8 +221,7 @@ class MenuView extends FrameLayout implements
}
// We can skip animating if FAB is not visible
- if (Flags.floatingMenuImeDisplacementAnimation()
- && animateMovement && getVisibility() == VISIBLE) {
+ if (animateMovement && getVisibility() == VISIBLE) {
mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
// onArrivalAtPosition() is called at the end of the animation.
} else {
@@ -331,7 +327,7 @@ class MenuView extends FrameLayout implements
mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
}
- if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+ if (!Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -431,22 +427,6 @@ class MenuView extends FrameLayout implements
onPositionChanged();
}
- void gotoEditScreen() {
- if (!Flags.floatingMenuDragToEdit()) {
- return;
- }
- mMenuAnimationController.flingMenuThenSpringToEdge(
- getMenuPosition().x, 100f, 0f);
-
- Intent intent = getIntentForEditScreen();
- PackageManager packageManager = getContext().getPackageManager();
- List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
- PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
- if (!activities.isEmpty()) {
- mContext.startActivity(intent);
- }
- }
-
void incrementTexMetricForAllTargets(String metric) {
if (!Flags.floatingMenuDragToEdit()) {
return;
@@ -461,23 +441,6 @@ class MenuView extends FrameLayout implements
Counter.logIncrementWithUid(metric, uid);
}
- Intent getIntentForEditScreen() {
- List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
- mSecureSettings.getStringForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- UserHandle.USER_CURRENT)).stream().toList();
-
- Intent intent = new Intent(
- Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
- Bundle args = new Bundle();
- Bundle fragmentArgs = new Bundle();
- fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
- args.putBundle(":settings:show_fragment_args", fragmentArgs);
- intent.replaceExtras(args);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return intent;
- }
-
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
return (InstantInsetLayerDrawable) getBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fcedc457..760e1c374e31 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@ import android.view.WindowMetrics;
import androidx.annotation.DimenRes;
-import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@ class MenuViewAppearance {
final int margin = getMenuMargin();
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
- if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
- // Initializes start position for mapping the translation of the menu view.
- draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
- }
-
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a68fb48..85bf784d623b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -36,19 +36,24 @@ import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
+import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.util.ArraySet;
import android.view.MotionEvent;
import android.view.View;
@@ -120,6 +125,7 @@ class MenuViewLayer extends FrameLayout implements
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
private final NotificationManager mNotificationManager;
+ private StatusBarManager mStatusBarManager;
private final MenuNotificationFactory mNotificationFactory;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
@@ -246,6 +252,7 @@ class MenuViewLayer extends FrameLayout implements
mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
mNotificationManager = context.getSystemService(NotificationManager.class);
+ mStatusBarManager = context.getSystemService(StatusBarManager.class);
if (Flags.floatingMenuDragToEdit()) {
mDragToInteractAnimationController = new DragToInteractAnimationController(
@@ -319,6 +326,9 @@ class MenuViewLayer extends FrameLayout implements
if (Flags.floatingMenuAnimatedTuck()) {
setClipChildren(true);
}
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -443,21 +453,18 @@ class MenuViewLayer extends FrameLayout implements
}
public void onMoveToTuckedChanged(boolean moveToTuck) {
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- if (moveToTuck) {
- final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
- final int[] location = getLocationOnScreen();
- bounds.offset(
- location[0],
- location[1]
- );
-
- setClipBounds(bounds);
- }
- // Instead of clearing clip bounds when moveToTuck is false,
- // wait until the spring animation finishes.
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
+
+ setClipBounds(bounds);
}
- // Function is a no-operation if flag is disabled.
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
}
private void onSpringAnimationsEndAction() {
@@ -475,9 +482,7 @@ class MenuViewLayer extends FrameLayout implements
setClipBounds(null);
}
}
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- mMenuView.onArrivalAtPosition(false);
- }
+ mMenuView.onArrivalAtPosition(false);
}
void dispatchAccessibilityAction(int id) {
@@ -490,7 +495,7 @@ class MenuViewLayer extends FrameLayout implements
mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
} else if (id == R.id.action_edit
&& Flags.floatingMenuDragToEdit()) {
- mMenuView.gotoEditScreen();
+ gotoEditScreen();
mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
}
mDismissView.hide();
@@ -499,6 +504,40 @@ class MenuViewLayer extends FrameLayout implements
id, /* scaleUp= */ false);
}
+ void gotoEditScreen() {
+ if (!Flags.floatingMenuDragToEdit()) {
+ return;
+ }
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ mMenuView.getMenuPosition().x, 100f, 0f);
+
+ Intent intent = getIntentForEditScreen();
+ PackageManager packageManager = getContext().getPackageManager();
+ List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+ if (!activities.isEmpty()) {
+ mContext.startActivity(intent);
+ mStatusBarManager.collapsePanels();
+ }
+ }
+
+ Intent getIntentForEditScreen() {
+ List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).stream().toList();
+
+ Intent intent = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ Bundle args = new Bundle();
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+ args.putBundle(":settings:show_fragment_args", fragmentArgs);
+ intent.replaceExtras(args);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ffd259b..6b1240b87b72 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
/**
@@ -88,14 +86,9 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- params.setFitInsetsTypes(0);
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- } else {
- params.setFitInsetsTypes(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- }
+ params.setFitInsetsTypes(0);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1880fe..8bd675c44943 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,7 +881,7 @@ public class AuthContainerView extends LinearLayout
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- if (Flags.customBiometricPrompt()) {
+ if (Flags.customBiometricPrompt() && constraintBp()) {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
removeWindowIfAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b87fadf995e6..4d88f4945952 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
@@ -151,6 +152,7 @@ constructor(
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e48f05d09fc4..7bb75bf5ca9b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.text.method.ScrollingMovementMethod
import android.util.Log
@@ -166,11 +167,14 @@ object BiometricViewBinder {
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- view.requireViewById(R.id.space_above_content),
- viewModel
- )
+
+ if (Flags.customBiometricPrompt() && constraintBp()) {
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
+ }
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 3469cfa210ba..e457601a6d52 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -86,7 +86,12 @@ object PromptIconViewBinder {
launch {
var width = 0
var height = 0
- viewModel.activeAuthType.collect { activeAuthType ->
+ combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect {
+ (_, activeAuthType) ->
+ // Every time after bp shows, [isIconViewLoaded] is set to false in
+ // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew
+ // (when size or activeAuthType changes), we need to update
+ // [isIconViewLoaded] here to keep it correct.
when (activeAuthType) {
AuthType.Fingerprint,
AuthType.Coex -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9c28f1c16546..9949e4c7d3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -141,6 +141,9 @@ constructor(
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
+ val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+ lottie.pauseAnimation()
+ lottie.removeAllLottieOnCompositionLoadedListener()
windowManager.get().removeView(overlayView)
overlayView = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 61aeffe03b5d..86b0b4455f61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,6 +29,7 @@ import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -217,18 +218,12 @@ constructor(
*/
val faceMode: Flow<Boolean> =
combine(modalities, isConfirmationRequired, fingerprintStartMode) {
- modalities: BiometricModalities,
- isConfirmationRequired: Boolean,
- fingerprintStartMode: FingerprintStartMode ->
- if (modalities.hasFaceAndFingerprint) {
- if (isConfirmationRequired) {
- false
- } else {
- !fingerprintStartMode.isStarted
- }
- } else {
- false
- }
+ modalities,
+ isConfirmationRequired,
+ fingerprintStartMode ->
+ modalities.hasFaceAndFingerprint &&
+ !isConfirmationRequired &&
+ fingerprintStartMode == FingerprintStartMode.Pending
}
.distinctUntilChanged()
@@ -248,14 +243,11 @@ constructor(
* asset to be loaded before determining the prompt size.
*/
val isIconViewLoaded: Flow<Boolean> =
- combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
- ->
- if (credentialKind is PromptKind.Biometric) {
- isIconViewLoaded
- } else {
- true
+ combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
+ val noIcon = modalities.isEmpty
+ noIcon || isIconViewLoaded
}
- }
+ .distinctUntilChanged()
// Sets whether the prompt's iconView animation has been loaded in the view yet.
fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
@@ -284,7 +276,7 @@ constructor(
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> null
+ !(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else ->
@@ -304,7 +296,7 @@ constructor(
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> ""
+ !(customBiometricPrompt() && constraintBp()) || it == null -> ""
it.logoDescription != null -> it.logoDescription
else ->
try {
@@ -329,7 +321,7 @@ constructor(
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
promptSelectorInteractor.prompt
- .map { if (customBiometricPrompt()) it?.contentView else null }
+ .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
.distinctUntilChanged()
private val originalDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index af32eb534155..000f03a8c6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -109,7 +109,8 @@ constructor(
biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
!keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
- !statusBarStateController.isDozing
+ !statusBarStateController.isDozing &&
+ !bouncerRepository.primaryBouncerShow.value
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748f8668..7f6fc914e92b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@ import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.Quint
import javax.inject.Inject
@@ -130,17 +92,22 @@ constructor(
repository.setMessage(
when (biometricSourceType) {
BiometricSourceType.FINGERPRINT ->
- incorrectFingerprintInput(currentSecurityMode)
+ BouncerMessageStrings.incorrectFingerprintInput(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
BiometricSourceType.FACE ->
- incorrectFaceInput(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.incorrectFaceInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
else ->
- defaultMessage(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
)
}
@@ -189,45 +156,79 @@ constructor(
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
if (wasRebootedForMainlineUpdate) {
- authRequiredForMainlineUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForMainlineUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
- authRequiredAfterReboot(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterReboot(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
}
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
- authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
- authRequiredAfterAdminLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterAdminLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (
- trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
) {
- authRequiredForUnattendedUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForUnattendedUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (fpLockedOut) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else if (faceLockedOut) {
if (isFaceAuthClass3) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else {
- faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.faceLockedOut(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
} else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
- authRequiredAfterAdaptiveAuthRequest(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
) {
- nonStrongAuthTimeout(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.nonStrongAuthTimeout(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
- authRequiredAfterUserLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterUserLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
defaultMessage
}
@@ -244,7 +245,11 @@ constructor(
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
- val message = primaryAuthLockedOut(currentSecurityMode)
+ val message =
+ BouncerMessageStrings.primaryAuthLockedOut(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
message.message?.animate = false
message.message?.formatterArgs =
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@ constructor(
if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
- incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.incorrectSecurityInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
)
}
@@ -285,7 +294,12 @@ constructor(
}
private val defaultMessage: BouncerMessageModel
- get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ get() =
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
fun onPrimaryBouncerUserInput() {
if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@ private fun defaultMessage(
return BouncerMessageModel(
message =
Message(
- messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+ messageResId =
+ BouncerMessageStrings.defaultMessage(
+ securityMode.toAuthModel(),
+ fpAuthIsAllowed
+ )
+ .toMessage()
+ .message
+ ?.messageResId,
animate = false
),
secondaryMessage = Message(message = secondaryMessage, animate = false)
)
}
-private fun defaultMessage(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- defaultMessageWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
- SecurityMode.Password -> Pair(keyguard_enter_password, 0)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- incorrectSecurityInputWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
- else
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOut(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabled(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
- SecurityMode.Password ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
- SecurityMode.PIN ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
return BouncerMessageModel(
message = Message(messageResId = this.first, animate = false),
secondaryMessage = Message(messageResId = this.second, animate = false)
)
}
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+ return when (this) {
+ SecurityMode.Invalid -> AuthenticationMethodModel.None
+ SecurityMode.None -> AuthenticationMethodModel.None
+ SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+ SecurityMode.Password -> AuthenticationMethodModel.Password
+ SecurityMode.PIN -> AuthenticationMethodModel.Pin
+ SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+ SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 8c87b0c78ea7..893887fad176 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -93,6 +93,8 @@ constructor(
val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
val keyguardAuthenticatedBiometrics: Flow<Boolean> =
repository.keyguardAuthenticatedBiometrics.filterNotNull()
+ val keyguardAuthenticatedBiometricsHandled: Flow<Unit> =
+ repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {}
val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index c3d4cb30e700..7d3075a9dd74 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -44,6 +44,8 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -70,6 +72,9 @@ constructor(
val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+ private val _bouncerMessageChanged = MutableSharedFlow<String?>()
+ val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged
+
/** Returns the default message for the sim pin screen. */
fun getDefaultMessage(): String {
val isEsimLocked = repository.isLockedEsim.value ?: false
@@ -81,7 +86,7 @@ constructor(
return ""
}
- var count = telephonyManager.activeModemCount
+ val count = telephonyManager.activeModemCount
val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
val displayName = info?.displayName
var msg: String =
@@ -156,32 +161,24 @@ constructor(
repository.setSimVerificationErrorMessage(null)
}
- /**
- * Based on sim state, unlock the locked sim with the given credentials.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- suspend fun verifySim(input: List<Any>): String? {
+ /** Based on sim state, unlock the locked sim with the given credentials. */
+ suspend fun verifySim(input: List<Any>) {
+ val code = input.joinToString(separator = "")
if (repository.isSimPukLocked.value) {
- return verifySimPuk(input.joinToString(separator = ""))
+ verifySimPuk(code)
+ } else {
+ verifySimPin(code)
}
-
- return verifySimPin(input.joinToString(separator = ""))
}
- /**
- * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- private suspend fun verifySimPin(input: String): String? {
+ /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */
+ private suspend fun verifySimPin(input: String) {
val subscriptionId = repository.subscriptionId.value
// A SIM PIN is 4 to 8 decimal digits according to
// GSM 02.17 version 5.0.1, Section 5.6 PIN Management
if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ return
}
val result =
withContext(backgroundDispatcher) {
@@ -190,8 +187,10 @@ constructor(
telephonyManager.supplyIccLockPin(input)
}
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ _bouncerMessageChanged.emit(null)
+ }
PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
@@ -199,24 +198,22 @@ constructor(
repository.setSimVerificationErrorMessage(
getPinPasswordErrorMessage(result.attemptsRemaining)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPinPasswordErrorMessage(result.attemptsRemaining)
+ _bouncerMessageChanged.emit(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
}
}
}
-
- return null
}
/**
* Verifies the input and unlocks the locked sim with a puk code instead of pin.
*
* This occurs after incorrectly verifying the sim pin multiple times.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
*/
- private suspend fun verifySimPuk(entry: String): String? {
+ private suspend fun verifySimPuk(entry: String) {
val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
val subscriptionId: Int = repository.subscriptionId.value
@@ -224,10 +221,11 @@ constructor(
if (enteredSimPuk == null) {
if (entry.length >= MIN_SIM_PUK_LENGTH) {
repository.setSimPukUserInput(enteredSimPuk = entry)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
+ return
}
// Stage 2: Set a new sim pin to lock the sim card.
@@ -237,10 +235,11 @@ constructor(
enteredSimPuk = enteredSimPuk,
enteredSimPin = entry,
)
- return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
+ return
}
// Stage 3: Confirm the newly set sim pin.
@@ -250,7 +249,8 @@ constructor(
resources.getString(R.string.kg_invalid_confirm_pin_hint)
)
repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
+ return
}
val result =
@@ -261,9 +261,11 @@ constructor(
resetSimPukUserInput()
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
- PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ _bouncerMessageChanged.emit(null)
+ }
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
// puk to the user.
@@ -274,17 +276,21 @@ constructor(
isEsimLocked = repository.isLockedEsim.value == true
)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPukPasswordErrorMessage(
- result.attemptsRemaining,
- isDefault = false,
- isEsimLocked = repository.isLockedEsim.value == true
+ _bouncerMessageChanged.emit(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
)
}
- else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+ else -> {
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed))
+ }
}
-
- return null
}
private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 000000000000..cb12ce50dd23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -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 com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+ get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+ get() = this.second
+
+object BouncerMessageStrings {
+ private val EmptyMessage = Pair(0, 0)
+
+ fun defaultMessage(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectSecurityInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+ return when (securityMode) {
+ Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+ Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+ Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+ else -> EmptyMessage
+ }
+ }
+
+ private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+ return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+ }
+
+ fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ val primaryMessage = R.string.kg_fp_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectFaceInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else {
+ val primaryMessage = R.string.bouncer_face_not_recognized
+ when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+ }
+
+ private fun incorrectFaceInputWithFingerprintAllowed(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.bouncer_face_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdminLockdown(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdaptiveAuthRequest(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForUnattendedUpdate(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterPrimaryAuthTimeout(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+ else -> EmptyMessage
+ }
+ }
+
+ fun nonStrongAuthTimeout(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_auth_timeout
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun faceLockedOut(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_face_locked_out
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun trustAgentDisabled(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_trust_agent_disabled
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pattern
+ )
+ Password ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_password
+ )
+ Pin ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pin
+ )
+ else -> EmptyMessage
+ }
+ }
+
+ private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+ else R.string.keyguard_enter_pattern
+ }
+
+ private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+ else R.string.keyguard_enter_pin
+ }
+
+ private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+ else R.string.keyguard_enter_password
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 1c9d1f01e89e..e1fea5f9c62a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -23,12 +23,14 @@ import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/** Models UI state for the lock screen bouncer; handles user input. */
+@ExperimentalCoroutinesApi
class KeyguardBouncerViewModel
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a0296ebdc..e910a9271ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -157,8 +157,7 @@ class PinBouncerViewModel(
if (authenticationMethod == AuthenticationMethodModel.Sim) {
viewModelScope.launch {
isSimUnlockingDialogVisible.value = true
- val msg = simBouncerInteractor.verifySim(getInput())
- interactor.setMessage(msg)
+ simBouncerInteractor.verifySim(getInput())
isSimUnlockingDialogVisible.value = false
clearInput()
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 3819e614aca0..4f4f3d0324b3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -97,6 +97,14 @@ class FalsingCollectorImpl implements FalsingCollector {
}
};
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateSensorRegistration();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@ class FalsingCollectorImpl implements FalsingCollector {
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@ class FalsingCollectorImpl implements FalsingCollector {
} else {
sessionEnd();
}
+ updateSensorRegistration();
+ }
+
+ private boolean shouldBeRegisteredToSensors() {
+ return mScreenOn
+ && (mState == StatusBarState.KEYGUARD
+ || (mState == StatusBarState.SHADE
+ && mKeyguardStateController.isOccluded()
+ && mKeyguardStateController.isShowing()))
+ && !mShowingAod;
+ }
+
+ private void updateSensorRegistration() {
+ if (shouldBeRegisteredToSensors()) {
+ registerSensors();
+ } else {
+ unregisterSensors();
+ }
}
private void sessionStart() {
@@ -371,7 +399,6 @@ class FalsingCollectorImpl implements FalsingCollector {
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
- registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
@@ -380,7 +407,6 @@ class FalsingCollectorImpl implements FalsingCollector {
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
- unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411c401d..ef686f91b36a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* A [CoreStartable] responsible for automatically navigating between communal scenes when certain
* conditions are met.
*/
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalSceneStartable
@Inject
@@ -50,9 +58,13 @@ constructor(
private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val systemSettings: SystemSettings,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
+ private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
override fun start() {
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,49 @@ constructor(
// }
// }
// .launchIn(bgScope)
+
+ systemSettings
+ .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+ // Read the setting value on start.
+ .emitOnStart()
+ .onEach {
+ screenTimeout =
+ systemSettings.getInt(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_TIMEOUT
+ )
+ }
+ .launchIn(bgScope)
+
+ // Handle timing out back to the dream.
+ bgScope.launch {
+ combine(
+ communalInteractor.desiredScene,
+ // Emit a value on start so the combine starts.
+ communalInteractor.userActivity.emitOnStart()
+ ) { scene, _ ->
+ // Time out should run whenever we're dreaming and the hub is open, even if not
+ // docked.
+ scene == CommunalScenes.Communal
+ }
+ // mapLatest cancels the previous action block when new values arrive, so any
+ // already running timeout gets cancelled when conditions change or user interaction
+ // is detected.
+ .mapLatest { shouldTimeout ->
+ if (!shouldTimeout) {
+ return@mapLatest false
+ }
+
+ delay(screenTimeout.milliseconds)
+ true
+ }
+ .sample(keyguardInteractor.isDreaming, ::Pair)
+ .collect { (shouldTimeout, isDreaming) ->
+ if (isDreaming && shouldTimeout) {
+ communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ }
+ }
+ }
}
private suspend fun determineSceneAfterTransition(
@@ -105,5 +160,6 @@ constructor(
companion object {
val AWAKE_DEBOUNCE_DELAY = 5.seconds
val DOCK_DEBOUNCE_DELAY = 1.seconds
+ val DEFAULT_SCREEN_TIMEOUT = 15000
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 940b48cc94c9..52025b177b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -63,10 +63,13 @@ import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -84,7 +87,7 @@ import kotlinx.coroutines.flow.shareIn
class CommunalInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application val applicationScope: CoroutineScope,
broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
@@ -152,6 +155,14 @@ constructor(
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
+ val _userActivity: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+ fun signalUserInteraction() {
+ _userActivity.tryEmit(Unit)
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 85f3c202f10f..c913300f5ace 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -45,6 +45,10 @@ abstract class BaseCommunalViewModel(
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ fun signalUserInteraction() {
+ communalInteractor.signalUserInteraction()
+ }
+
fun onSceneChanged(scene: SceneKey) {
communalInteractor.onSceneChanged(scene)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd7718c..1003050caf7c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -69,6 +69,7 @@ import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.toast.ToastModule;
import com.android.systemui.unfold.SysUIUnfoldStartableModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -117,6 +118,7 @@ import javax.inject.Named;
ShadeModule.class,
StartCentralSurfacesModule.class,
SceneContainerFrameworkModule.class,
+ SysUICoroutinesModule.class,
SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 96171aa6566e..d495facdfe9d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -31,9 +32,22 @@ class DeviceEntryBiometricSettingsInteractor
constructor(
repository: BiometricSettingsRepository,
) {
+
+ /**
+ * Flags that control the device entry authentication behavior.
+ *
+ * This exposes why biometrics may not be currently allowed.
+ */
+ val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags
+
+ /** Whether the current user has enrolled and enabled fingerprint auth. */
+ val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> =
+ repository.isFingerprintEnrolledAndEnabled
+
val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
repository.isFingerprintAuthCurrentlyAllowed
- val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
+ /** Whether the current user has enrolled and enabled face auth. */
+ val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
/** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25bf0e52..7733de49ce9c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -32,6 +32,10 @@ interface DeviceEntryFaceAuthInteractor {
/** Current detection status */
val detectionStatus: Flow<FaceDetectionStatus>
+ val lockedOut: Flow<Boolean>
+
+ val authenticated: Flow<Boolean>
+
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5f6f7c77a38..805999397282 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -23,14 +25,20 @@ import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationS
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceEntryFingerprintAuthInteractor
@Inject
constructor(
repository: DeviceEntryFingerprintAuthRepository,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
) {
/** Whether fingerprint authentication is currently running or not */
val isRunning: Flow<Boolean> = repository.isRunning
@@ -47,4 +55,21 @@ constructor(
repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+
+ /**
+ * Whether fingerprint authentication is currently allowed for the user. This is true if the
+ * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+ * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
+ * too many incorrect attempts.
+ */
+ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
+
+ /**
+ * Whether the fingerprint sensor is present under the display as opposed to being on the power
+ * button or behind/rear of the phone.
+ */
+ val isSensorUnderDisplay =
+ fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 029a4f33cd27..fa2421a3516d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,24 +16,30 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -55,10 +61,13 @@ constructor(
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
- trustRepository: TrustRepository,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val trustInteractor: TrustInteractor,
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
* Whether the device is unlocked.
@@ -96,8 +105,8 @@ constructor(
*/
private val isPassivelyAuthenticated =
merge(
- trustRepository.isCurrentUserTrusted,
- deviceEntryFaceAuthRepository.isAuthenticated,
+ trustInteractor.isTrusted,
+ faceAuthInteractor.authenticated,
)
.onStart { emit(false) }
@@ -134,6 +143,67 @@ constructor(
initialValue = null,
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.lockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ::Quad
+ )
+ .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -187,4 +257,12 @@ constructor(
}
}
}
+
+ private val wasRebootedForMainlineUpdate
+ get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index fd6fbc9610f4..98deda09613e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -77,7 +77,7 @@ constructor(
private fun startUpdatingFaceHelpMessageDeferral() {
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceAcquired
@@ -94,7 +94,7 @@ constructor(
}
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceHelp
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b9416690a85..65f3eb762693 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -31,10 +31,10 @@ import kotlinx.coroutines.flow.emptyFlow
*/
@SysUISingleton
class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
- override val authenticationStatus: Flow<FaceAuthenticationStatus>
- get() = emptyFlow()
- override val detectionStatus: Flow<FaceDetectionStatus>
- get() = emptyFlow()
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow()
+ override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
+ override val lockedOut: Flow<Boolean> = emptyFlow()
+ override val authenticated: Flow<Boolean> = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc27cc5d..a7266503b7a1 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -272,6 +272,8 @@ constructor(
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
+ override val lockedOut: Flow<Boolean> = repository.isLockedOut
+ override val authenticated: Flow<Boolean> = repository.isAuthenticated
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
new file mode 100644
index 000000000000..5b672ac372db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/** List of reasons why device entry can be restricted to certain authentication methods. */
+enum class DeviceEntryRestrictionReason {
+ /**
+ * Reason: Lockdown initiated by the user.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UserLockdown,
+
+ /**
+ * Reason: Not unlocked since reboot.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceReboot,
+
+ /**
+ * Reason: Not unlocked since reboot after a mainline update.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceMainlineUpdate,
+
+ /**
+ * Reason: Lockdown initiated by admin through installed device policy
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ PolicyLockdown,
+
+ /**
+ * Reason: Device entry credentials need to be used for an unattended update at a later point in
+ * time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UnattendedUpdate,
+
+ /**
+ * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ SecurityTimeout,
+
+ /**
+ * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect
+ * authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ StrongBiometricsLockedOut,
+
+ /**
+ * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method
+ * was locked out after many incorrect authentication attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongFaceLockedOut,
+
+ /**
+ * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry
+ * method and a stronger authentication method wasn't used to unlock the device for a prolonged
+ * period of time.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongBiometricsSecurityTimeout,
+
+ /**
+ * Reason: A trust agent that was granting trust has either expired or disabled by the user by
+ * opening the power menu.
+ *
+ * Restriction: Only non trust agent device entry methods are allowed.
+ */
+ TrustAgentDisabled,
+
+ /**
+ * Reason: Theft protection is enabled after too many unlock attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ */
+ AdaptiveAuthRequest,
+
+ /**
+ * Reason: Bouncer was locked out after too many incorrect authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ BouncerLockedOut,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
index 6cd94c623ff7..14525269eb6b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.launch
class HomeControlsDreamStartable
@Inject
constructor(
- private val context: Context,
+ context: Context,
private val packageManager: PackageManager,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
@Background private val bgScope: CoroutineScope,
@@ -39,10 +39,13 @@ constructor(
private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
override fun start() {
- if (!homePanelDream()) return
bgScope.launch {
- homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
- setEnableHomeControlPanel(selectedPanelComponent != null)
+ if (homePanelDream()) {
+ homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+ setEnableHomeControlPanel(selectedPanelComponent != null)
+ }
+ } else {
+ setEnableHomeControlPanel(false)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6fa20de1fb7f..1e3c6049edd4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -20,36 +20,34 @@ import android.os.SystemProperties
import javax.inject.Inject
import javax.inject.Singleton
-/**
- * Proxy to make {@link SystemProperties} easily testable.
- */
+/** Proxy to make {@link SystemProperties} easily testable. */
@Singleton
open class SystemPropertiesHelper @Inject constructor() {
- fun get(name: String): String {
+ open fun get(name: String): String {
return SystemProperties.get(name)
}
- fun get(name: String, def: String?): String {
+ open fun get(name: String, def: String?): String {
return SystemProperties.get(name, def)
}
- fun getBoolean(name: String, default: Boolean): Boolean {
+ open fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
- fun setBoolean(name: String, value: Boolean) {
+ open fun setBoolean(name: String, value: Boolean) {
SystemProperties.set(name, if (value) "1" else "0")
}
- fun set(name: String, value: String) {
+ open fun set(name: String, value: String) {
SystemProperties.set(name, value)
}
- fun set(name: String, value: Int) {
+ open fun set(name: String, value: Int) {
set(name, value.toString())
}
- fun erase(name: String) {
+ open fun erase(name: String) {
set(name, "")
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 000000000000..0143b85a4fbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+ const val INVALID_DURATION = 0 /* in ms */
+
+ private const val TAG = "LongPressHapticBuilder"
+ private const val SPIN_SCALE = 0.2f
+ private const val CLICK_SCALE = 0.5f
+ private const val LOW_TICK_SCALE = 0.08f
+ private const val WARMUP_TIME = 75 /* in ms */
+ private const val DAMPING_TIME = 24 /* in ms */
+
+ /** Create the signal that indicates that a long-press action is available. */
+ fun createLongPressHint(
+ lowTickDuration: Int,
+ spinDuration: Int,
+ effectDuration: Int
+ ): VibrationEffect? {
+ if (lowTickDuration == 0 || spinDuration == 0) {
+ Log.d(
+ TAG,
+ "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+ )
+ return null
+ }
+ if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+ Log.d(
+ TAG,
+ "Cannot fit long-press hint signal in the effect duration. No signal created",
+ )
+ return null
+ }
+
+ val nLowTicks = WARMUP_TIME / lowTickDuration
+ val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+ val composition = VibrationEffect.startComposition()
+
+ // Warmup low ticks
+ repeat(nLowTicks) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE,
+ 0,
+ )
+ }
+
+ // Spin effect
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+ // Damping low ticks
+ repeat(rampDownLowTicks) { i ->
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE / (i + 1),
+ 0,
+ )
+ }
+
+ return composition.compose()
+ }
+
+ /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+ fun createSnapEffect(): VibrationEffect? =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+ .compose()
+
+ /** Creates a signal that indicates the reversal of the long-press animation. */
+ fun createReversedEffect(
+ pausedProgress: Float,
+ lowTickDuration: Int,
+ effectDuration: Int,
+ ): VibrationEffect? {
+ val duration = pausedProgress * effectDuration
+ if (duration == 0f) return null
+
+ if (lowTickDuration == 0) {
+ Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+ return null
+ }
+
+ val nLowTicks = (duration / lowTickDuration).toInt()
+ if (nLowTicks == 0) return null
+
+ val composition = VibrationEffect.startComposition()
+ var scale: Float
+ val step = LOW_TICK_SCALE / nLowTicks
+ repeat(nLowTicks) { i ->
+ scale = max(LOW_TICK_SCALE - step * i, 0f)
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+ }
+ return composition.compose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 000000000000..ec72a1422973
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+ private val vibratorHelper: VibratorHelper?,
+ private val effectDuration: Int,
+) : View.OnTouchListener {
+
+ /** Current state */
+ var state = State.IDLE
+ @VisibleForTesting set
+
+ /** Flows for view control and action */
+ private val _effectProgress = MutableStateFlow<Float?>(null)
+ val effectProgress = _effectProgress.asStateFlow()
+
+ private val _actionType = MutableStateFlow<ActionType?>(null)
+ val actionType = _actionType.asStateFlow()
+
+ /** Haptic effects */
+ private val durations =
+ vibratorHelper?.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN
+ )
+
+ private val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+ durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+ effectDuration
+ )
+
+ private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ /* A coroutine scope and a timer job that waits for the pressedTimeout */
+ var scope: CoroutineScope? = null
+ private var waitJob: Job? = null
+
+ private val effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
+
+ doOnStart { handleAnimationStart() }
+ addUpdateListener { _effectProgress.value = animatedValue as Float }
+ doOnEnd { handleAnimationComplete() }
+ doOnCancel { handleAnimationCancel() }
+ }
+
+ private fun reverse() {
+ val pausedProgress = effectAnimator.animatedFraction
+ val effect =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ durations?.get(0) ?: 0,
+ effectDuration,
+ )
+ vibratorHelper?.cancel()
+ vibrate(effect)
+ effectAnimator.reverse()
+ }
+
+ private fun vibrate(effect: VibrationEffect?) {
+ if (vibratorHelper != null && effect != null) {
+ vibratorHelper.vibrate(effect)
+ }
+ }
+
+ /**
+ * Handle relevant touch events for the operation of a Tile.
+ *
+ * A click action is performed following the relevant logic that originates from the
+ * [MotionEvent.ACTION_UP] event depending on the current state.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> handleActionDown()
+ MotionEvent.ACTION_UP -> handleActionUp()
+ MotionEvent.ACTION_CANCEL -> handleActionCancel()
+ }
+ return true
+ }
+
+ private fun handleActionDown() {
+ when (state) {
+ State.IDLE -> {
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+ State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+ else -> {}
+ }
+ }
+
+ private fun startPressedTimeoutWait() {
+ waitJob =
+ scope?.launch {
+ try {
+ delay(PRESSED_TIMEOUT)
+ handleTimeoutComplete()
+ } catch (_: CancellationException) {
+ state = State.IDLE
+ }
+ }
+ }
+
+ private fun handleActionUp() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ _actionType.value = ActionType.CLICK
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleActionCancel() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleAnimationStart() {
+ vibrate(longPressHint)
+ state = State.RUNNING_FORWARD
+ }
+
+ /** This function is called both when an animator completes or gets cancelled */
+ private fun handleAnimationComplete() {
+ if (state == State.RUNNING_FORWARD) {
+ vibrate(snapEffect)
+ _actionType.value = ActionType.LONG_PRESS
+ _effectProgress.value = null
+ }
+ if (state != State.TIMEOUT_WAIT) {
+ // This will happen if the animator did not finish by being cancelled
+ state = State.IDLE
+ }
+ }
+
+ private fun handleAnimationCancel() {
+ _effectProgress.value = 0f
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+
+ private fun handleTimeoutComplete() {
+ if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+ effectAnimator.start()
+ }
+ }
+
+ fun clearActionType() {
+ _actionType.value = null
+ }
+
+ enum class State {
+ IDLE, /* The effect is idle waiting for touch input */
+ TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+ RUNNING_FORWARD, /* The effect is running normally */
+ RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+ }
+
+ /* A type of action to perform on the view depending on the effect's state and logic */
+ enum class ActionType {
+ CLICK,
+ LONG_PRESS,
+ }
+
+ companion object {
+ /**
+ * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+ * inside a scrollable container, they will be considered pressed only after a tap timeout.
+ */
+ val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 000000000000..e298154159b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+ fun bind(
+ tile: QSTileViewImpl,
+ effect: QSLongPressEffect?,
+ ) {
+ if (effect == null) return
+
+ tile.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ effect.scope = this
+
+ launch {
+ effect.effectProgress.collect { progress ->
+ progress?.let {
+ if (it == 0f) {
+ tile.bringToFront()
+ }
+ tile.updateLongPressEffectProperties(it)
+ }
+ }
+ }
+
+ launch {
+ effect.actionType.collect { action ->
+ action?.let {
+ when (it) {
+ QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+ QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+ }
+ effect.clearActionType()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index f5f5571dbb1b..882f2315f6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -175,7 +175,6 @@ constructor(
pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
- pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
/** UserId of the current selected user. */
@@ -324,22 +323,14 @@ constructor(
else isNonStrongBiometricAllowed
}
- private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
- selectedUserId
- .flatMapLatest { userId ->
- devicePolicyChangedForAllUsers
- .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- }
- .stateIn(
- scope,
- started = SharingStarted.Eagerly,
- initialValue =
- devicePolicyManager.isFingerprintDisabled(
- userRepository.getSelectedUserInfo().id
- )
- )
+ private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
+ selectedUserId.flatMapLatest { userId ->
+ devicePolicyChangedForAllUsers
+ .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 0e487d297b40..9c68c45476d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -159,6 +159,11 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
lastAnimator?.cancel()
lastAnimator = info.animator
+ // Cancel any existing manual transitions
+ updateTransitionId?.let { uuid ->
+ updateTransition(uuid, lastStep.value, TransitionState.CANCELED)
+ }
+
info.animator?.let { animator ->
// An animator was provided, so use it to run the transition
animator.setFloatValues(startingValue, 1f)
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 143edf972cb0..773497383f0b 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
@@ -57,6 +57,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
@@ -179,11 +180,19 @@ constructor(
/** Keyguard can be clipped at the top as the shade is dragged */
val topClippingBounds: Flow<Int?> =
- combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
- _,
- topClippingBounds ->
- topClippingBounds
- }
+ combineTransform(
+ configurationInteractor.onAnyConfigurationChange,
+ keyguardTransitionInteractor
+ .transitionValue(GONE)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ repository.topClippingBounds
+ ) { _, isGone, topClippingBounds ->
+ if (!isGone) {
+ emit(topClippingBounds)
+ }
+ }
+ .distinctUntilChanged()
/** Last point that [KeyguardRootView] view was tapped */
val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index c28e49db9d37..68ea5d047e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -34,7 +34,7 @@ private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
class KeyguardTransitionAuditLogger
@Inject
constructor(
- @Application private val scope: CoroutineScope,
+ @Background private val scope: CoroutineScope,
private val interactor: KeyguardTransitionInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val logger: KeyguardLogger,
@@ -70,6 +70,12 @@ constructor(
}
scope.launch {
+ sharedNotificationContainerViewModel.bounds.collect {
+ logger.log(TAG, VERBOSE, "Notif: bounds", it)
+ }
+ }
+
+ scope.launch {
sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect {
logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 00902b419a97..e6655ee3898f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -38,9 +39,12 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -50,6 +54,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Encapsulates business-logic related to the keyguard transitions. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,6 +63,7 @@ class KeyguardTransitionInteractor
@Inject
constructor(
@Application val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
@@ -70,6 +76,30 @@ constructor(
) {
private val TAG = this::class.simpleName
+ private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+ /**
+ * Numerous flows are derived from, or care directly about, the transition value in and out of a
+ * single state. This prevent the redundant filters from running.
+ */
+ private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
+ return transitionValueCache.getOrPut(state) {
+ MutableSharedFlow<Float>(
+ extraBufferCapacity = 2,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ }
+ }
+
+ init {
+ scope.launch(mainDispatcher) {
+ repository.transitions.collect { step ->
+ getTransitionValueFlow(step.from).emit(1f - step.value)
+ getTransitionValueFlow(step.to).emit(step.value)
+ }
+ }
+ }
+
/** (any)->GONE transition information */
val anyStateToGoneTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == GONE }
@@ -353,15 +383,7 @@ constructor(
fun transitionValue(
state: KeyguardState,
): Flow<Float> {
- return repository.transitions
- .filter { it.from == state || it.to == state }
- .map {
- if (it.from == state) {
- 1 - it.value
- } else {
- it.value
- }
- }
+ return getTransitionValueFlow(state)
}
fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
new file mode 100644
index 000000000000..2ff6e165293b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+@SysUISingleton
+class TrustInteractor @Inject constructor(repository: TrustRepository) {
+ /**
+ * Whether the current user has a trust agent enabled. This is true if the user has at least one
+ * trust agent enabled in settings.
+ */
+ val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged
+
+ /**
+ * Whether the current user's trust agent is currently allowed, this will be false if trust
+ * agent is disabled for any reason (security timeout, disabled on lock screen by opening the
+ * power menu, etc), it does not include temporary biometric lockouts.
+ */
+ val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged
+
+ /** Whether the current user is trusted by any of the enabled trust agents. */
+ val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
index 08904b6ffa86..d6f3634fdcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -32,6 +32,9 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+ val isPrimaryAuthRequiredAfterLockout =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
+
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
@@ -47,7 +50,7 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
- val primaryAuthRequiredForUnattendedUpdate =
+ val isPrimaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d40021007a2b..3a2781c81f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -102,9 +102,16 @@ object AlternateBouncerViewBinder {
launch {
viewModel.scrimAlpha.collect {
+ val wasVisible = alternateBouncerViewContainer.visibility == View.VISIBLE
alternateBouncerViewContainer.visibility =
if (it < .1f) View.INVISIBLE else View.VISIBLE
scrim.viewAlpha = it
+ if (
+ wasVisible && alternateBouncerViewContainer.visibility == View.INVISIBLE
+ ) {
+ // view is no longer visible
+ viewModel.hideAlternateBouncer()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 2526f0aec796..10a9e3bba7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -89,4 +89,8 @@ constructor(
fun showPrimaryBouncer() {
statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
}
+
+ fun hideAlternateBouncer() {
+ statusBarKeyguardViewManager.hideAlternateBouncer(false)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b596419efe..6042117eff12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -72,13 +72,19 @@ constructor(
duration = TO_LOCKSCREEN_DURATION,
onStep = { value -> -translatePx + value * translatePx },
interpolator = EMPHASIZED,
- onCancel = { -translatePx.toFloat() },
+ // Move notifications back to their original position since they can be
+ // accessed from the shade, and also keyguard elements in case the animation
+ // is cancelled.
+ onFinish = { 0f },
+ onCancel = { 0f },
name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
)
}
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc460c..c4383fc0857d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@ constructor(
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@ constructor(
lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
transitionInteractor: KeyguardTransitionInteractor,
) {
@@ -110,6 +112,7 @@ constructor(
occludedToLockscreenTransitionViewModel.shortcutsAlpha,
offToLockscreenTransitionViewModel.shortcutsAlpha,
primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+ glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
)
/** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@ constructor(
lockscreenToGoneTransitionViewModel.shortcutsAlpha,
lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+ lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
shadeExpansionAlpha,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 288ef3c52e21..993e81bfbf69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,9 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,9 +33,10 @@ import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -44,37 +51,69 @@ constructor(
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
- deviceEntryInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+ initialValue =
+ destinationScenes(
+ isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+ isCommunalAvailable = false,
+ shadeMode = shadeInteractor.shadeMode.value,
+ ),
)
- private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
- }
+ private fun destinationScenes(
+ isDeviceUnlocked: Boolean,
+ isCommunalAvailable: Boolean,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val quickSettingsIfSingleShade =
+ if (shadeMode is ShadeMode.Single) {
+ Scenes.QuickSettings
+ } else {
+ Scenes.Shade
+ }
- /** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: StateFlow<SceneKey?> =
- communalInteractor.isCommunalAvailable
- .map { available -> if (available) Scenes.Communal else null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ return mapOf(
+ Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
- /** The key of the scene we should switch to when swiping down from the top edge. */
- val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
- shadeInteractor.shadeMode
- .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
+ // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+ swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+ swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+ // Swiping down, not from the edge, always navigates to the shade scene.
+ swipeDown(pointerCount = 1) to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
)
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
+ }
+
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
+
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e2a825..5cbc1d4a3745 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -71,7 +71,8 @@ constructor(
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
onStep = { value -> value * translatePx },
// Move notifications back to their original position since they can be
- // accessed from the shade.
+ // accessed from the shade, and also keyguard elements in case the animation
+ // is cancelled.
onFinish = { 0f },
onCancel = { 0f },
interpolator = EMPHASIZED,
@@ -81,6 +82,8 @@ constructor(
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 027a739b4abf..bf225633fa86 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import javax.inject.Inject
-class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
+ val isMediaVisible: Boolean
+ get() = mediaDataManager.hasActiveMediaOrRecommendation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index f4903f1f054f..768bb8e2e917 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,7 @@ import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
@@ -537,6 +539,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
@@ -575,6 +578,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mShadeViewController);
+ mView.setComponents(mShadeViewController, mPanelExpansionInteractor);
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a21f079..1927f4932ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,6 +75,7 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -149,7 +150,9 @@ public class NavigationBarView extends FrameLayout {
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
@Nullable
- private ShadeViewController mPanelView;
+ private ShadeViewController mShadeViewController;
+ @Nullable
+ private PanelExpansionInteractor mPanelExpansionInteractor;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
@@ -347,8 +350,9 @@ public class NavigationBarView extends FrameLayout {
}
/** */
- public void setComponents(ShadeViewController panel) {
- mPanelView = panel;
+ public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+ mShadeViewController = svc;
+ mPanelExpansionInteractor = pei;
updatePanelSystemUiStateFlags();
}
@@ -750,10 +754,10 @@ public class NavigationBarView extends FrameLayout {
private void updatePanelSystemUiStateFlags() {
if (SysUiState.DEBUG) {
- Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+ Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController);
}
- if (mPanelView != null) {
- mPanelView.updateSystemUiStateFlags();
+ if (mShadeViewController != null) {
+ mShadeViewController.updateSystemUiStateFlags();
}
}
@@ -801,7 +805,8 @@ public class NavigationBarView extends FrameLayout {
*/
void updateSlippery() {
setSlippery(!isQuickStepSwipeUpEnabled() ||
- (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+ (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+ && !mPanelExpansionInteractor.isCollapsing()));
}
void setSlippery(boolean slippery) {
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 91c86dff34ea..9d0ea5ebd925 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@ import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
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;
@@ -54,7 +55,6 @@ import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -104,6 +104,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,7 +152,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
+ private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private boolean mLogGesture = false;
@@ -256,7 +264,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mInRejectedExclusion = false;
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
- && !mGestureBlockingActivityRunning
+ && !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
&& !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags,
- mIsTrackpadThreeFingerSwipe),
- mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+ mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+ mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
- pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+ pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mUseMLModel=" + mUseMLModel);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651555d7..cd6511979375 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ VibratorHelper vibratorHelper) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+ vibratorHelper);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871bd006..5e12b9d4cc34 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private SplitShadeStateController mSplitShadeStateController;
+ private final VibratorHelper mVibratorHelper;
+
@VisibleForTesting
protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
UiEventLogger uiEventLogger,
QSLogger qsLogger,
DumpManager dumpManager,
- SplitShadeStateController splitShadeStateController
+ SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view);
mHost = host;
@@ -158,6 +162,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mSplitShadeStateController = splitShadeStateController;
mShouldUseSplitNotificationShade =
mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+ mVibratorHelper = vibratorHelper;
}
@Override
@@ -300,7 +305,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
private void addTile(final QSTile tile, boolean collapsedView) {
- final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+ final QSTileViewImpl tileView = new QSTileViewImpl(
+ getContext(), collapsedView, mVibratorHelper);
final TileRecord r = new TileRecord(tile, tileView);
// TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
// b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da5d288..05bb08813cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
@@ -56,10 +57,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+ DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 000000000000..a2ded6a6aacf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+ var xScale: Float,
+ var yScale: Float,
+ var cornerRadius: Float,
+ var backgroundColor: Int,
+ var labelColor: Int,
+ var secondaryLabelColor: Int,
+ var chevronColor: Int,
+ var overlayColor: Int,
+ var iconColor: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b2adb2..145674747bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -587,7 +587,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
name = "handleClick";
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
+ mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
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 6cc682ae3c96..63963ded2923 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@ import android.content.res.Resources.ID_NULL
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.os.Trace
@@ -36,6 +37,7 @@ import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
@@ -48,9 +50,12 @@ import androidx.annotation.VisibleForTesting
import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
import com.android.systemui.FontSizeUtils
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -58,12 +63,15 @@ import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
import java.util.Objects
private const val TAG = "QSTileViewImpl"
open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
- private val collapsed: Boolean = false
+ private val collapsed: Boolean = false,
+ private val vibratorHelper: VibratorHelper? = null,
) : QSTileView(context), HeightOverrideable, LaunchableView {
companion object {
@@ -163,6 +171,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
+ private var lastIconTint = 0
private val launchableViewDelegate = LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
@@ -171,6 +180,12 @@ open class QSTileViewImpl @JvmOverloads constructor(
private val locInScreen = IntArray(2)
+ /** Visuo-haptic long-press effects */
+ private var longPressEffect: QSLongPressEffect? = null
+ private var initialLongPressProperties: QSLongPressProperties? = null
+ private var finalLongPressProperties: QSLongPressProperties? = null
+ private val colorEvaluator = ArgbEvaluator.getInstance()
+
init {
val typedValue = TypedValue()
if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -339,6 +354,9 @@ open class QSTileViewImpl @JvmOverloads constructor(
true
}
)
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ }
}
private fun init(
@@ -589,6 +607,27 @@ open class QSTileViewImpl @JvmOverloads constructor(
lastState = state.state
lastDisabledByPolicy = state.disabledByPolicy
+ lastIconTint = icon.getColor(state)
+
+ // Long-press effects
+ if (quickSettingsVisualHapticsLongpress()){
+ if (state.handlesLongClick) {
+ // initialize the long-press effect and set it as the touch listener
+ showRippleEffect = false
+ initializeLongPressEffect()
+ setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(this, longPressEffect)
+ } else {
+ // Long-press effects might have been enabled before but the new state does not
+ // handle a long-press. In this case, we go back to the behaviour of a regular tile
+ // and clean-up the resources
+ showRippleEffect = isClickable
+ setOnTouchListener(null)
+ longPressEffect = null
+ initialLongPressProperties = null
+ finalLongPressProperties = null
+ }
+ }
}
private fun setAllColors(
@@ -709,6 +748,140 @@ open class QSTileViewImpl @JvmOverloads constructor(
}
}
+ override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+ fun updateLongPressEffectProperties(effectProgress: Float) {
+ if (!isLongClickable) return
+ setAllColors(
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.backgroundColor ?: 0,
+ finalLongPressProperties?.backgroundColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.labelColor ?: 0,
+ finalLongPressProperties?.labelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.secondaryLabelColor ?: 0,
+ finalLongPressProperties?.secondaryLabelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.chevronColor ?: 0,
+ finalLongPressProperties?.chevronColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.overlayColor ?: 0,
+ finalLongPressProperties?.overlayColor ?: 0,
+ ) as Int,
+ )
+ icon.setTint(
+ icon.mIcon as ImageView,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.iconColor ?: 0,
+ finalLongPressProperties?.iconColor ?: 0,
+ ) as Int,
+ )
+
+ val newScaleX =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newScaleY =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newRadius =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.cornerRadius ?: 0f,
+ finalLongPressProperties?.cornerRadius ?: 0f,
+ )
+ scaleX = newScaleX
+ scaleY = newScaleY
+ for (child in children) {
+ child.scaleX = 1f / newScaleX
+ child.scaleY = 1f / newScaleY
+ }
+ changeCornerRadius(newRadius)
+ }
+
+ private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+ start + fraction * (end - start)
+
+ private fun resetLongPressEffectProperties() {
+ scaleY = 1f
+ scaleX = 1f
+ for (child in children) {
+ child.scaleY = 1f
+ child.scaleX = 1f
+ }
+ changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+ setAllColors(
+ getBackgroundColorForState(lastState, lastDisabledByPolicy),
+ getLabelColorForState(lastState, lastDisabledByPolicy),
+ getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+ getChevronColorForState(lastState, lastDisabledByPolicy),
+ getOverlayColorForState(lastState),
+ )
+ icon.setTint(icon.mIcon as ImageView, lastIconTint)
+ }
+
+ private fun initializeLongPressEffect() {
+ initializeLongPressProperties()
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+ )
+ }
+
+ private fun initializeLongPressProperties() {
+ initialLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1f,
+ /* yScale= */1f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+ getBackgroundColorForState(lastState),
+ getLabelColorForState(lastState),
+ getSecondaryLabelColorForState(lastState),
+ getChevronColorForState(lastState),
+ getOverlayColorForState(lastState),
+ lastIconTint,
+ )
+
+ finalLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1.1f,
+ /* yScale= */1.2f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+ getBackgroundColorForState(Tile.STATE_ACTIVE),
+ getLabelColorForState(Tile.STATE_ACTIVE),
+ getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+ getChevronColorForState(Tile.STATE_ACTIVE),
+ getOverlayColorForState(Tile.STATE_ACTIVE),
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+ )
+ }
+
+ private fun changeCornerRadius(radius: Float) {
+ for (i in 0 until backgroundDrawable.numberOfLayers) {
+ val layer = backgroundDrawable.getDrawable(i)
+ if (layer is GradientDrawable) {
+ layer.cornerRadius = radius
+ }
+ }
+ }
+
@VisibleForTesting
internal fun getCurrentColors(): List<Int> = listOf(
backgroundColor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index bd66843e1217..d82b1755ac80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.RecordIssueDialogDelegate
@@ -66,6 +67,7 @@ constructor(
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
private val userContextProvider: UserContextProvider,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
@@ -138,6 +140,8 @@ constructor(
.create {
isRecording = true
startIssueRecordingService(it.screenRecord, it.winscopeTracing)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
refreshState()
}
.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 32deb30d926b..6b654beea149 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -34,11 +34,11 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -186,7 +186,7 @@ public class UserDetailView extends PseudoGridView {
(UserRecord) view.getTag();
if (userRecord.isDisabledByAdmin()) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, userRecord.enforcedAdmin);
+ userRecord.enforcedAdmin);
mController.startActivity(intent);
} else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index d1f8945cc091..87b89ea6810a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -96,10 +96,7 @@ constructor(
is PolicyResult.TileEnabled -> false
is PolicyResult.TileDisabled -> {
val intent =
- RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- context,
- policyResult.admin
- )
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin)
activityStarter.postStartActivityDismissingKeyguard(intent, 0)
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
index dcae088fecf7..1247854da61d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
@@ -30,7 +30,7 @@ constructor(
private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
) {
- val isEnabled = bluetoothAutoOnRepository.getValue.map { it == ENABLED }.distinctUntilChanged()
+ val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
/**
* Checks if the auto on value is present in the repository.
@@ -49,7 +49,7 @@ constructor(
Log.e(TAG, "Trying to set toggle value while feature not available.")
} else {
val newValue = if (value) ENABLED else DISABLED
- bluetoothAutoOnRepository.setValue(newValue)
+ bluetoothAutoOnRepository.setAutoOn(newValue)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
index e17b4d3f376b..f97fc389b12c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
@@ -16,8 +16,6 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
-import android.os.UserHandle
-import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -27,17 +25,21 @@ import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-/** Repository class responsible for managing the Bluetooth Auto-On feature settings. */
-// TODO(b/316822488): Handle multi-user
+/**
+ * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
+ * user.
+ */
@SysUISingleton
class BluetoothAutoOnRepository
@Inject
@@ -47,21 +49,24 @@ constructor(
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- // Flow representing the auto on setting value
- internal val getValue: Flow<Int> =
- secureSettings
- .observerFlow(UserHandle.USER_SYSTEM, SETTING_NAME)
- .onStart { emit(Unit) }
- .map {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- return@map UNSET
- }
- secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM)
+
+ // Flow representing the auto on setting value for the current user
+ @OptIn(ExperimentalCoroutinesApi::class)
+ internal val isAutoOn: StateFlow<Int> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ secureSettings
+ .observerFlow(userInfo.id, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
}
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+ UNSET
+ )
/**
* Checks if the auto on setting value is ever set for the current user.
@@ -70,12 +75,11 @@ constructor(
*/
suspend fun isValuePresent(): Boolean =
withContext(backgroundDispatcher) {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- false
- } else {
- secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM) != UNSET
- }
+ secureSettings.getIntForUser(
+ SETTING_NAME,
+ UNSET,
+ userRepository.getSelectedUserInfo().id
+ ) != UNSET
}
/**
@@ -83,18 +87,17 @@ constructor(
*
* @param value The new setting value to be applied.
*/
- suspend fun setValue(value: Int) {
+ suspend fun setAutoOn(value: Int) {
withContext(backgroundDispatcher) {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- } else {
- secureSettings.putIntForUser(SETTING_NAME, value, UserHandle.USER_SYSTEM)
- }
+ secureSettings.putIntForUser(
+ SETTING_NAME,
+ value,
+ userRepository.getSelectedUserInfo().id
+ )
}
}
companion object {
- private const val TAG = "BluetoothAutoOnRepository"
const val SETTING_NAME = "bluetooth_automatic_turn_on"
const val UNSET = -1
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 0d9b702b49ec..7009816942f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,6 +19,7 @@ package com.android.systemui.recordissue
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.content.pm.LauncherApps
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
@@ -26,8 +27,10 @@ import android.os.UserHandle
import android.util.Log
import androidx.core.content.FileProvider
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.RecordingService
@@ -53,7 +56,9 @@ constructor(
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
userContextProvider: UserContextProvider,
- keyguardDismissUtil: KeyguardDismissUtil
+ keyguardDismissUtil: KeyguardDismissUtil,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
) :
RecordingService(
controller,
@@ -93,10 +98,16 @@ constructor(
}
ACTION_STOP,
ACTION_STOP_NOTIF -> {
+ // ViewCapture needs to save it's data before it is disabled, or else the data will
+ // be lost. This is expected to change in the near future, and when that happens
+ // this line should be removed.
+ getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
}
ACTION_SHARE -> {
shareRecording(intent)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
// Unlike all other actions, action_share has different behavior for the screen
// recording qs tile than it does for the record issue qs tile. Return sticky to
@@ -119,13 +130,11 @@ constructor(
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- if (mNotificationId != NOTIF_BASE_ID) {
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
- }
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ff18a1128802..7313a49be1bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -28,8 +28,8 @@ import android.view.WindowManager
import android.widget.Button
import android.widget.PopupMenu
import android.widget.Switch
-import androidx.annotation.AnyThread
import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
@@ -74,6 +74,7 @@ constructor(
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
private lateinit var issueTypeButton: Button
+ private var hasSelectedIssueType: Boolean = false
@MainThread
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -82,15 +83,21 @@ constructor(
setTitle(context.getString(R.string.qs_record_issue_label))
setIcon(R.drawable.qs_record_issue_icon_off)
setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
- setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
- onStarted.accept(
- IssueRecordingConfig(
- screenRecordSwitch.isChecked,
- true /* TODO: Base this on issueType selected */
- )
- )
- dismiss()
- }
+ setPositiveButton(
+ R.string.qs_record_issue_start,
+ { _, _ ->
+ if (hasSelectedIssueType) {
+ onStarted.accept(
+ IssueRecordingConfig(
+ screenRecordSwitch.isChecked,
+ true /* TODO: Base this on issueType selected */
+ )
+ )
+ dismiss()
+ }
+ },
+ false
+ )
}
}
@@ -104,49 +111,47 @@ constructor(
screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
- onScreenRecordSwitchClicked(context, isEnabled)
+ if (isEnabled) {
+ bgExecutor.execute { onScreenRecordSwitchClicked() }
+ }
}
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
}
}
- @AnyThread
- private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
- if (!isEnabled) return
-
- bgExecutor.execute {
- if (
- flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
- devicePolicyResolver
- .get()
- .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
- ) {
- mainExecutor.execute {
- screenCaptureDisabledDialogDelegate.createDialog().show()
- screenRecordSwitch.isChecked = false
- }
- return@execute
+ @WorkerThread
+ private fun onScreenRecordSwitchClicked() {
+ if (
+ flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+ devicePolicyResolver
+ .get()
+ .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+ ) {
+ mainExecutor.execute {
+ screenCaptureDisabledDialogDelegate.createDialog().show()
+ screenRecordSwitch.isChecked = false
}
+ return
+ }
- mediaProjectionMetricsLogger.notifyProjectionInitiated(
- userTracker.userId,
- SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
- )
+ mediaProjectionMetricsLogger.notifyProjectionInitiated(
+ userTracker.userId,
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+ )
- if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
- val prefs =
- userFileManager.getSharedPreferences(
- RecordIssueTile.TILE_SPEC,
- Context.MODE_PRIVATE,
- userTracker.userId
- )
- if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
- mainExecutor.execute {
- ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
- setOnCancelListener { screenRecordSwitch.isChecked = false }
- show()
- }
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+ val prefs =
+ userFileManager.getSharedPreferences(
+ RecordIssueTile.TILE_SPEC,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+ mainExecutor.execute {
+ ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+ setOnCancelListener { screenRecordSwitch.isChecked = false }
+ show()
}
}
}
@@ -174,5 +179,6 @@ constructor(
setForceShowIcon(true)
show()
}
+ hasSelectedIssueType = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index f01e9bea1372..1f6d2122ebfc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -21,9 +21,7 @@ import android.app.Notification
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.util.Log
-import android.view.Display
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -32,45 +30,53 @@ import android.view.ViewTreeObserver
import android.view.WindowInsets
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Legacy implementation of screenshot view methods. Just proxies the calls down into the original
* ScreenshotView.
*/
-class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) :
- ScreenshotViewProxy {
+class LegacyScreenshotViewProxy
+@AssistedInject
+constructor(
+ private val logger: UiEventLogger,
+ flags: FeatureFlags,
+ @Assisted private val context: Context,
+ @Assisted private val displayId: Int
+) : ScreenshotViewProxy {
override val view: ScreenshotView =
LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
override val screenshotPreview: View
-
- override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
- set(value) {
- view.setDefaultDisplay(value)
- }
- override var defaultTimeoutMillis: Long = 6000
- set(value) {
- view.setDefaultTimeoutMillis(value)
- }
- override var flags: FeatureFlags? = null
- set(value) {
- view.setFlags(value)
- }
override var packageName: String = ""
set(value) {
+ field = value
view.setPackageName(value)
}
override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
set(value) {
+ field = value
view.setCallbacks(value)
}
override var screenshot: ScreenshotData? = null
set(value) {
- view.setScreenshot(value)
+ field = value
+ value?.let {
+ val badgeBg =
+ AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+ val user = it.userHandle
+ if (badgeBg != null && user != null) {
+ view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+ }
+ view.setScreenshot(it)
+ }
}
override val isAttachedToWindow
@@ -82,6 +88,8 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
init {
view.setUiEventLogger(logger)
+ view.setDefaultDisplay(displayId)
+ view.setFlags(flags)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
if (LogConfig.DEBUG_WINDOW) {
@@ -95,8 +103,6 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
- override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
-
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
view.createScreenshotDropInAnimation(screenRect, showFlash)
@@ -130,14 +136,17 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
- ) =
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
+ ) {
view.prepareScrollingTransition(
response,
screenBitmap,
newScreenshot,
screenshotTakenInPortrait
)
+ view.post { onTransitionPrepared.run() }
+ }
override fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -155,10 +164,19 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
- override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
-
- override fun post(runnable: Runnable) {
- view.post(runnable)
+ override fun prepareEntranceAnimation(runnable: Runnable) {
+ view.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "onPreDraw: startAnimation")
+ }
+ view.viewTreeObserver.removeOnPreDrawListener(this)
+ runnable.run()
+ return true
+ }
+ }
+ )
}
private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
@@ -166,7 +184,7 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "Predictive Back callback dispatched")
}
- onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
}
view.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
@@ -201,7 +219,7 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: $keyCode")
}
- onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
return true
}
return false
@@ -210,10 +228,9 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog
)
}
- class Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy {
- return LegacyScreenshotViewProxy(context, logger)
- }
+ @AssistedFactory
+ interface Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6bab956ca09a..198a29c4ed5b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -228,7 +228,7 @@ public class ScreenshotController {
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private final WindowContext mContext;
private final FeatureFlags mFlags;
@@ -344,7 +344,7 @@ public class ScreenshotController {
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
- mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -460,7 +460,7 @@ public class ScreenshotController {
attachWindow();
- boolean showFlash = true;
+ boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (screenshot.getScreenBounds() != null
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
@@ -472,15 +472,14 @@ public class ScreenshotController {
screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
screenshot.getBitmap().getHeight()));
}
+ } else {
+ showFlash = true;
}
- prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
- mMessageContainerController.onScreenshotTaken(screenshot);
- });
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
- mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background),
- screenshot.getUserHandle()));
mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
@@ -596,9 +595,6 @@ public class ScreenshotController {
setWindowFocusable(false);
}
});
- mViewProxy.setFlags(mFlags);
- mViewProxy.setDefaultDisplay(mDisplayId);
- mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
@@ -606,22 +602,6 @@ public class ScreenshotController {
setContentView(mViewProxy.getView());
}
- private void prepareAnimation(Rect screenRect, boolean showFlash,
- Runnable onAnimationComplete) {
- mViewProxy.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation");
- }
- mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash, onAnimationComplete);
- return true;
- }
- });
- }
-
private void enqueueScrollCaptureRequest(UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -706,10 +686,14 @@ public class ScreenshotController {
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
- mScreenshotTakenInPortrait);
- // delay starting scroll capture to make sure the scrim is up before the app moves
- mViewProxy.post(() -> runBatchScrollCapture(response, owner));
+ if (newScreenshot != null) {
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(
+ response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+ () -> runBatchScrollCapture(response, owner));
+ } else {
+ Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ }
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8a8766dbab94..1c5a8a1a9fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
import static java.util.Objects.requireNonNull;
@@ -33,6 +34,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.Notification;
@@ -168,7 +170,6 @@ public class ScreenshotView extends FrameLayout implements
private ScreenshotData mScreenshotData;
private final InteractionJankMonitor mInteractionJankMonitor;
- private long mDefaultTimeoutOfTimeoutHandler;
private FeatureFlags mFlags;
private final Bundle mInteractiveBroadcastOption;
@@ -244,10 +245,6 @@ public class ScreenshotView extends FrameLayout implements
return InteractionJankMonitor.getInstance();
}
- void setDefaultTimeoutMillis(long timeout) {
- mDefaultTimeoutOfTimeoutHandler = timeout;
- }
-
public void hideScrollChip() {
mScrollChip.setVisibility(View.GONE);
}
@@ -755,7 +752,7 @@ public class ScreenshotView extends FrameLayout implements
InteractionJankMonitor.Configuration.Builder.withView(
CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
.setTag("Actions")
- .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+ .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mInteractionJankMonitor.begin(builder);
}
});
@@ -781,7 +778,7 @@ public class ScreenshotView extends FrameLayout implements
return animator;
}
- void badgeScreenshot(Drawable badge) {
+ void badgeScreenshot(@Nullable Drawable badge) {
mScreenshotBadge.setImageDrawable(badge);
mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index d5c7f95ce289..182b8894677a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -21,23 +21,16 @@ import android.app.Notification
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.view.ScrollCaptureResponse
import android.view.View
import android.view.ViewGroup
-import android.view.ViewTreeObserver
import android.view.WindowInsets
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
/** Abstraction of the surface between ScreenshotController and ScreenshotView */
interface ScreenshotViewProxy {
val view: ViewGroup
val screenshotPreview: View
- var defaultDisplay: Int
- var defaultTimeoutMillis: Long
- var flags: FeatureFlags?
var packageName: String
var callbacks: ScreenshotView.ScreenshotViewCallback?
var screenshot: ScreenshotData?
@@ -49,7 +42,6 @@ interface ScreenshotViewProxy {
fun reset()
fun updateInsets(insets: WindowInsets)
fun updateOrientation(insets: WindowInsets)
- fun badgeScreenshot(userBadgedIcon: Drawable)
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
@@ -61,7 +53,8 @@ interface ScreenshotViewProxy {
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
)
fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -73,10 +66,9 @@ interface ScreenshotViewProxy {
fun stopInputListening()
fun requestFocus()
fun announceForAccessibility(string: String)
- fun getViewTreeObserver(): ViewTreeObserver
- fun post(runnable: Runnable)
+ fun prepareEntranceAnimation(runnable: Runnable)
interface Factory {
- fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy
+ fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4a5cb9..5df6c45295b6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@ public class TimeoutHandler extends Handler {
private final Context mContext;
private Runnable mOnTimeout;
- private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+ int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
@Inject
public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d43b9e..cdb9abb15e84 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -86,7 +86,8 @@ public abstract class ScreenshotModule {
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
@Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
- return new LegacyScreenshotViewProxy.Factory();
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
+ LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
+ return legacyScreenshotViewProxyFactory;
}
}
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 861a2edebf14..539b0c2dd599 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -19,6 +19,7 @@ package com.android.systemui.settings.brightness;
import static com.android.systemui.Flags.hapticBrightnessSlider;
import android.content.Context;
+import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -33,6 +34,7 @@ import com.android.systemui.Gefingerpoken;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +64,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
private final UiEventLogger mUiEventLogger;
private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final ActivityStarter mActivityStarter;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
@@ -84,11 +87,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
- SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
+ SeekableSliderHapticPlugin brightnessSliderHapticPlugin,
+ ActivityStarter activityStarter) {
super(brightnessSliderView);
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
+ mActivityStarter = activityStarter;
}
/**
@@ -131,7 +136,15 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
@Override
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mView.setEnforcedAdmin(admin);
+ if (admin == null) {
+ mView.setAdminBlocker(null);
+ } else {
+ mView.setAdminBlocker(() -> {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ });
+ }
}
private void setMirror(ToggleSlider toggleSlider) {
@@ -259,18 +272,21 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
+ private final ActivityStarter mActivityStarter;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
- SystemClock clock
+ SystemClock clock,
+ ActivityStarter activityStarter
) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
+ mActivityStarter = activityStarter;
}
/**
@@ -292,7 +308,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
if (hapticBrightnessSlider()) {
HapticSliderViewBinder.bind(viewRoot, plugin);
}
- return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
+ return new BrightnessSliderController(
+ root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
}
/** Get the layout to inflate based on what slider to use */
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 c43d20cdf52f..92006a473ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -31,7 +31,6 @@ import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
@@ -120,9 +119,8 @@ public class BrightnessSliderView extends FrameLayout {
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mSlider.setEnabled(admin == null);
- mSlider.setEnforcedAdmin(admin);
+ void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+ mSlider.setAdminBlocker(blocker);
}
/**
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 a5a0ae70045e..288ff09602f4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -17,20 +17,15 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
-
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
- private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+ private AdminBlocker mAdminBlocker;
public ToggleSeekBar(Context context) {
super(context);
@@ -46,10 +41,7 @@ public class ToggleSeekBar extends SeekBar {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mEnforcedAdmin != null) {
- Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ if (mAdminBlocker != null && mAdminBlocker.block()) {
return true;
}
if (!isEnabled()) {
@@ -71,7 +63,12 @@ public class ToggleSeekBar extends SeekBar {
}
}
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mEnforcedAdmin = admin;
+ void setAdminBlocker(AdminBlocker blocker) {
+ mAdminBlocker = blocker;
+ setEnabled(blocker == null);
+ }
+
+ interface AdminBlocker {
+ boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b8675500a351..8b791de429ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1753,10 +1753,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
+ boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
if (migrateClocksToBlueprint()) {
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
return;
}
- boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
ConstraintLayout layout = mNotificationContainerParent;
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index de21a73e312b..8197b660c2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -37,12 +37,6 @@ interface ShadeViewController {
*/
val isPanelExpanded: Boolean
- /** Returns whether the shade is in the process of collapsing. */
- val isCollapsing: Boolean
-
- /** Returns whether shade's height is zero. */
- val isFullyCollapsed: Boolean
-
/** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
val isTracking: Boolean
@@ -102,19 +96,6 @@ interface ShadeViewController {
fun showAodUi()
/**
- * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
- * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
- * should be explicit about the what state we're checking.
- *
- * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
- */
- @Deprecated(
- "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
- "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
- )
- fun isFullyExpanded(): Boolean
-
- /**
* Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
*
* This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index b67156f4b982..48a2d75ec84a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -33,31 +33,34 @@ class ShadeViewControllerEmptyImpl @Inject constructor() :
ShadeBackActionInteractor,
ShadeLockscreenInteractor,
PanelExpansionInteractor {
- override fun expandToNotifications() {}
- override val isExpanded: Boolean = false
+ @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+ @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
override val isPanelExpanded: Boolean = false
override fun animateCollapseQs(fullyCollapse: Boolean) {}
override fun canBeCollapsed(): Boolean = false
- override val isCollapsing: Boolean = false
+ @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
override val isFullyCollapsed: Boolean = false
override val isTracking: Boolean = false
override val isViewEnabled: Boolean = false
override fun shouldHideStatusBarIconsWhenExpanded() = false
- override fun blockExpansionForCurrentTouch() {}
+ @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
override fun startExpandLatencyTracking() {}
override fun startBouncerPreHideAnimation() {}
override fun dozeTimeTick() {}
override fun resetViews(animate: Boolean) {}
override val barState: Int = 0
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
override fun closeUserSwitcherIfOpen(): Boolean {
return false
}
override fun onBackPressed() {}
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
override fun onBackProgressed(progressFraction: Float) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
- override fun setPulsing(pulsing: Boolean) {}
+ @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
override fun updateSystemUiStateFlags() {}
@@ -66,14 +69,18 @@ class ShadeViewControllerEmptyImpl @Inject constructor() :
override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
override fun transitionToExpandedShade(delay: Long) {}
- override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes")
override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
- override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("TODO(b/325072511) delete this")
override fun setKeyguardStatusBarAlpha(alpha: Float) {}
override fun showAodUi() {}
- override fun isFullyExpanded(): Boolean {
- return false
- }
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = false
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
@@ -84,6 +91,7 @@ class ShadeViewControllerEmptyImpl @Inject constructor() :
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+ @Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index 01118bd1406f..79ffe06f7923 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -41,4 +41,23 @@ interface PanelExpansionInteractor {
* backwards-compatibility and should not be consumed by newer code.
*/
@Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+
+ /**
+ * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+ * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+ * should be explicit about the what state we're checking.
+ *
+ * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+ */
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ val isFullyExpanded: Boolean
+
+ /** Returns whether shade's height is zero. */
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") val isFullyCollapsed: Boolean
+
+ /** Returns whether the shade is in the process of collapsing. */
+ @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 20f73b00d8a7..3ad2b5607b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -35,6 +35,8 @@ class PanelExpansionInteractorImpl
@Inject
constructor(
sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ shadeAnimationInteractor: ShadeAnimationInteractor,
) : PanelExpansionInteractor {
/**
@@ -93,6 +95,19 @@ constructor(
}
}
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
+ override val isFullyCollapsed = !shadeInteractor.isAnyExpanded.value
+
+ @Deprecated("Use ShadeAnimationInteractor instead")
+ override val isCollapsing =
+ shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+ shadeAnimationInteractor.isLaunchingActivity.value
private fun SceneKey.isExpandable(): Boolean {
return this == Scenes.Shade || this == Scenes.QuickSettings
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 5a777e8574d6..134c983f7b30 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,5 +37,5 @@ abstract class ShadeAnimationInteractor(
* completes the close. Important: if QS is collapsing back to shade, this will be false because
* that is not considered "closing".
*/
- abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+ abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index 2a7658a8eed7..f364d6ddf939 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -19,7 +19,7 @@ package com.android.systemui.shade.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -28,5 +28,5 @@ class ShadeAnimationInteractorEmptyImpl
constructor(
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- override val isAnyCloseAnimationRunning = flowOf(false)
+ override val isAnyCloseAnimationRunning = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index eaac8ae9dd3a..d9982e39e958 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -18,21 +18,26 @@ package com.android.systemui.shade.domain.interactor
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
constructor(
+ @Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
@@ -56,4 +61,5 @@ constructor(
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 6414af36b4dd..421a76163346 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -46,7 +46,10 @@ constructor(
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
repository: ShadeRepository,
) : BaseShadeInteractor {
- /** The amount [0-1] that the shade has been opened */
+ /**
+ * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
+ * in downstream flows.
+ */
override val shadeExpansion: Flow<Float> =
combine(
repository.lockscreenShadeExpansion,
@@ -71,6 +74,7 @@ constructor(
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, 0f)
override val qsExpansion: StateFlow<Float> = repository.qsExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 072f56d2429d..dcfccd8398b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -61,7 +61,7 @@ public class MediaCoordinator implements Coordinator {
return false;
}
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
inflateOrUpdateIcons(entry);
}
@@ -73,14 +73,14 @@ public class MediaCoordinator implements Coordinator {
@Override
public void onEntryInit(@NonNull NotificationEntry entry) {
// We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
}
@Override
public void onEntryAdded(@NonNull NotificationEntry entry) {
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -94,7 +94,7 @@ public class MediaCoordinator implements Coordinator {
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -120,7 +120,7 @@ public class MediaCoordinator implements Coordinator {
break;
case STATE_ICONS_INFLATED:
try {
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
} catch (InflationException e) {
reportInflationError(entry, e);
mIconsState.put(entry, STATE_ICONS_ERROR);
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 6400ff6b5c24..4bbe0357b335 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
@@ -138,7 +138,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
if (entry.rowExists()) {
mLogger.logUpdatingRow(entry, params);
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a5f42bb99e10..a900e45adbe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,14 +28,24 @@ import android.view.View
import android.widget.ImageView
import com.android.app.tracing.traceSection
import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Inflates and updates icons associated with notifications
@@ -53,9 +63,18 @@ class IconManager
constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
- private val iconBuilder: IconBuilder
+ private val iconBuilder: IconBuilder,
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Main private val mainCoroutineContext: CoroutineContext,
) : ConversationIconManager {
private var unimportantConversationKeys: Set<String> = emptySet()
+ /**
+ * A map of running jobs for fetching the person avatar from launcher. The key is the
+ * notification entry key.
+ */
+ private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
+ ConcurrentHashMap<String, Job>()
fun attach() {
notifCollection.addCollectionListener(entryListener)
@@ -136,13 +155,23 @@ constructor(
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) =
+ fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) =
traceSection("IconManager.updateIcons") {
if (!entry.icons.areIconsAvailable) {
return@traceSection
}
- entry.icons.smallIconDescriptor = null
- entry.icons.peopleAvatarDescriptor = null
+
+ if (usingCache && !Flags.notificationsBackgroundIcons()) {
+ Log.wtf(
+ TAG,
+ "Updating using the cache is not supported when the " +
+ "notifications_background_conversation_icons flag is off"
+ )
+ }
+ if (!usingCache || !Flags.notificationsBackgroundIcons()) {
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
+ }
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
val notificationContentDescription =
@@ -188,7 +217,7 @@ constructor(
@Throws(InflationException::class)
private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
val n = entry.sbn.notification
- val showPeopleAvatar = isImportantConversation(entry) && !redact
+ val showPeopleAvatar = !redact && isImportantConversation(entry)
val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
val smallIconDescriptor = entry.icons.smallIconDescriptor
@@ -208,26 +237,18 @@ constructor(
})
?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
- val ic =
- StatusBarIcon(
- entry.sbn.user,
- entry.sbn.packageName,
- icon,
- n.iconLevel,
- n.number,
- iconBuilder.getIconContentDescription(n)
- )
+ val sbi = icon.toStatusBarIcon(entry)
// Cache if important conversation.
if (isImportantConversation(entry)) {
if (showPeopleAvatar) {
- entry.icons.peopleAvatarDescriptor = ic
+ entry.icons.peopleAvatarDescriptor = sbi
} else {
- entry.icons.smallIconDescriptor = ic
+ entry.icons.smallIconDescriptor = sbi
}
}
- return ic
+ return sbi
}
@Throws(InflationException::class)
@@ -243,16 +264,69 @@ constructor(
}
}
+ private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon {
+ val n = entry.sbn.notification
+ return StatusBarIcon(
+ entry.sbn.user,
+ entry.sbn.packageName,
+ /* icon = */ this,
+ n.iconLevel,
+ n.number,
+ iconBuilder.getIconContentDescription(n)
+ )
+ }
+
+ private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
+ withContext(bgCoroutineContext) {
+ var icon: Icon? = null
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ try {
+ icon = launcherApps.getShortcutIcon(shortcut)
+ } catch (e: Exception) {
+ Log.e(
+ TAG,
+ "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+ )
+ }
+ }
+
+ // Once we have the icon, updating it should happen on the main thread.
+ if (icon != null) {
+ withContext(mainCoroutineContext) {
+ val iconDescriptor = icon.toStatusBarIcon(entry)
+
+ // Cache the value
+ entry.icons.peopleAvatarDescriptor = iconDescriptor
+
+ // Update the icons using the cached value
+ updateIcons(entry = entry, usingCache = true)
+ }
+ }
+ }
+
@Throws(InflationException::class)
- private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
+ private fun createPeopleAvatar(entry: NotificationEntry): Icon {
var ic: Icon? = null
- val shortcut = entry.ranking.conversationShortcutInfo
- if (shortcut != null) {
- ic = launcherApps.getShortcutIcon(shortcut)
+ if (Flags.notificationsBackgroundIcons()) {
+ // Ideally we want to get the icon from launcher, but this is a binder transaction that
+ // may take longer so let's kick it off on a background thread and use a placeholder in
+ // the meantime.
+ // Cancel the previous job if necessary.
+ launcherPeopleAvatarIconJobs[entry.key]?.cancel()
+ launcherPeopleAvatarIconJobs[entry.key] =
+ applicationCoroutineScope
+ .launch { getLauncherShortcutIconForPeopleAvatar(entry) }
+ .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } }
+ } else {
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ ic = launcherApps.getShortcutIcon(shortcut)
+ }
}
- // Fall back to extract from message
+ // Try to extract from message
if (ic == null) {
val extras: Bundle = entry.sbn.notification.extras
val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 2707ed83b74e..b77748e2990b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.interruption.FullScreenIntent
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_USER_SETUP_INCOMPLETE
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT
@@ -101,6 +102,7 @@ class FullScreenIntentDecisionProvider(
FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
FSI_LOCKED_SHADE(true, "locked shade"),
FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
+ FSI_USER_SETUP_INCOMPLETE(true, "user setup incomplete"),
NO_FSI_NO_HUN_OR_KEYGUARD(
false,
"no HUN or keyguard",
@@ -189,6 +191,10 @@ class FullScreenIntentDecisionProvider(
return FSI_DEVICE_NOT_PROVISIONED
}
+ if (!deviceProvisionedController.isCurrentUserSetup) {
+ return FSI_USER_SETUP_INCOMPLETE
+ }
+
return NO_FSI_NO_HUN_OR_KEYGUARD
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index b0155f13dbec..c084482bec9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -104,7 +104,11 @@ public interface NotificationInterruptStateProvider {
/**
* The device is not provisioned, launch FSI.
*/
- FSI_NOT_PROVISIONED(true);
+ FSI_NOT_PROVISIONED(true),
+ /**
+ * The current user has not completed setup, launch FSI.
+ */
+ FSI_USER_SETUP_INCOMPLETE(true);
public final boolean shouldLaunch;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index dc9eeb35565a..a655c7246638 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -362,6 +362,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
suppressedByDND);
}
+ // The current user hasn't completed setup, launch FSI.
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE,
+ suppressedByDND);
+ }
+
// Detect the case determined by b/231322873 to launch FSI while device is in use,
// as blocked by the correct implementation, and report the event.
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 589537ef713b..c05c3c3df2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -353,7 +353,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
nowExpanded = !isExpanded();
setUserExpanded(nowExpanded);
}
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
}
@@ -776,7 +776,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.updateGroupOverflow();
}
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isHeadsUp) {
mMustStayOnScreen = true;
@@ -824,7 +824,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mChildrenContainer != null) {
mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
}
@@ -1086,7 +1086,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (pinned) {
setAnimationRunning(true);
@@ -2609,7 +2609,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
onExpansionChanged(true /* userAction */, wasExpanded);
if (!wasExpanded && isExpanded()
&& getActualHeight() != getIntrinsicHeight()) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -2621,7 +2621,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
updateShelfIconColor();
}
@@ -2659,7 +2659,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
@@ -2678,7 +2678,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2835,7 +2835,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
&& (intrinsicBefore != 0 || getActualHeight() > 0)) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
@@ -2878,7 +2878,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mSensitiveHiddenInGeneral = hideSensitive;
int intrinsicAfter = getIntrinsicHeight();
if (intrinsicBefore != intrinsicAfter) {
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -3015,7 +3015,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (isChildInGroup()) {
mGroupExpansionManager.setGroupExpanded(mEntry, true);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
public void setChildrenExpanded(boolean expanded, boolean animate) {
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 77e94257c832..947976299f8e 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
@@ -49,6 +49,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.Trace;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -208,6 +209,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
private String mLastUpdateSidePaddingDumpString;
+ private long mLastUpdateSidePaddingElapsedRealtime;
+ private String mLastInitViewDumpString;
+ private long mLastInitViewElapsedRealtime;
/**
* The algorithm which calculates the properties for our children
@@ -887,17 +891,34 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mOverflingDistance = configuration.getScaledOverflingDistance();
Resources res = context.getResources();
+ final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
- && res.getBoolean(R.bool.is_small_screen_landscape);
+ && isSmallScreenLandscape;
// TODO (b/293252410) remove condition here when flag is launched
// Instead update the config_skinnyNotifsInLandscape to be false whenever
// is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape.
+ final boolean configSkinnyNotifsInLandscape = res.getBoolean(
+ R.bool.config_skinnyNotifsInLandscape);
if (useSmallLandscapeLockscreenResources) {
mSkinnyNotifsInLandscape = false;
} else {
- mSkinnyNotifsInLandscape = res.getBoolean(
- R.bool.config_skinnyNotifsInLandscape);
+ mSkinnyNotifsInLandscape = configSkinnyNotifsInLandscape;
}
+
+ mLastInitViewDumpString =
+ "mIsSmallLandscapeLockscreenEnabled=" + mIsSmallLandscapeLockscreenEnabled
+ + " isSmallScreenLandscape=" + isSmallScreenLandscape
+ + " useSmallLandscapeLockscreenResources="
+ + useSmallLandscapeLockscreenResources
+ + " skinnyNotifsInLandscape=" + configSkinnyNotifsInLandscape
+ + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
+ mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
+
+ if (DEBUG_UPDATE_SIDE_PADDING) {
+ Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
+ + mLastInitViewDumpString);
+ }
+
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mStackScrollAlgorithm.initView(context);
mStateAnimator.initView(context);
@@ -925,22 +946,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
+ " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
+ " orientation=" + orientation;
+ mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
if (DEBUG_UPDATE_SIDE_PADDING) {
- Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+ Log.v(TAG,
+ "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
+ + ": " + mLastUpdateSidePaddingDumpString);
}
- if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
+ if (viewWidth == 0) {
+ Log.e(TAG, "updateSidePadding: viewWidth is zero");
mSidePaddings = mMinimumPaddings;
return;
}
- // Portrait is easy, just use the dimen for paddings
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
mSidePaddings = mMinimumPaddings;
return;
}
+ if (mShouldUseSplitNotificationShade) {
+ if (mSkinnyNotifsInLandscape) {
+ Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!");
+ }
+ mSidePaddings = mMinimumPaddings;
+ return;
+ }
+
final int innerWidth = viewWidth - mMinimumPaddings * 2;
final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -4884,6 +4916,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void dump(PrintWriter pwOriginal, String[] args) {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
pw.println("Internal state:");
DumpUtilsKt.withIncreasedIndent(pw, () -> {
println(pw, "pulsing", mPulsing);
@@ -4914,7 +4947,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
println(pw, "minimumPaddings", mMinimumPaddings);
println(pw, "qsTilePadding", mQsTilePadding);
println(pw, "sidePaddings", mSidePaddings);
+ println(pw, "elapsedRealtime", elapsedRealtime);
+ println(pw, "lastInitView", mLastInitViewDumpString);
+ println(pw, "lastInitViewElapsedRealtime", mLastInitViewElapsedRealtime);
+ println(pw, "lastInitViewMillisAgo", elapsedRealtime - mLastInitViewElapsedRealtime);
+ println(pw, "shouldUseSplitNotificationShade", mShouldUseSplitNotificationShade);
println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
+ println(pw, "lastUpdateSidePaddingElapsedRealtime",
+ mLastUpdateSidePaddingElapsedRealtime);
+ println(pw, "lastUpdateSidePaddingMillisAgo",
+ elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
+ println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
mNotificationStackSizeCalculator.dump(pw, args);
});
pw.println();
@@ -5482,6 +5525,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAmbientState.setUseSplitShade(split);
updateDismissBehavior();
updateUseRoundedRectClipping();
+ requestLayout();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ece7a7fccc66..5b8b91c6a5fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.view.View
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
@@ -118,13 +116,6 @@ object SharedNotificationContainerBinder {
"SharedNotificationContainerVB (collapseFadeIn)"
)
}
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- viewModel.setShadeCollapseFadeInComplete(true)
- }
- }
- )
start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 2745817d6d40..2c4813aec99d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -67,7 +67,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -79,6 +78,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.isActive
@@ -124,6 +124,23 @@ constructor(
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
/**
+ * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
+ * as the legacy implementation has extra logic that produces incorrect results.
+ */
+ private val isAnyExpanded =
+ combine(
+ shadeInteractor.shadeExpansion.map { it > 0f },
+ shadeInteractor.qsExpansion.map { it > 0f },
+ ) { shadeExpansion, qsExpansion ->
+ shadeExpansion || qsExpansion
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ /**
* Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
* both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
* before the other.
@@ -131,17 +148,17 @@ constructor(
private val isShadeLocked: Flow<Boolean> =
combine(
keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
- shadeInteractor.qsExpansion.map { it > 0f },
- shadeInteractor.shadeExpansion.map { it > 0f },
- ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
- isShadeLocked && (isQsExpanded || isShadeExpanded)
+ isAnyExpanded,
+ ) { isShadeLocked, isAnyExpanded ->
+ isShadeLocked && isAnyExpanded
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
.dumpWhileCollecting("isShadeLocked")
- private val shadeCollapseFadeInComplete =
- MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
-
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
.map {
@@ -176,20 +193,16 @@ constructor(
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
- .distinctUntilChanged()
+ .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
.dumpWhileCollecting("isOnLockscreen")
/** Are we purely on the keyguard without the shade/qs? */
val isOnLockscreenWithoutShade: Flow<Boolean> =
combine(
isOnLockscreen,
- // Shade with notifications
- shadeInteractor.shadeExpansion.map { it > 0f },
- // Shade without notifications, quick settings only (pull down from very top on
- // lockscreen)
- shadeInteractor.qsExpansion.map { it > 0f },
- ) { isKeyguard, isShadeVisible, qsExpansion ->
- isKeyguard && !(isShadeVisible || qsExpansion)
+ isAnyExpanded,
+ ) { isKeyguard, isAnyExpanded ->
+ isKeyguard && !isAnyExpanded
}
.stateIn(
scope = applicationScope,
@@ -219,13 +232,9 @@ constructor(
val isOnGlanceableHubWithoutShade: Flow<Boolean> =
combine(
isOnGlanceableHub,
- // Shade with notifications
- shadeInteractor.shadeExpansion.map { it > 0f },
- // Shade without notifications, quick settings only (pull down from very top on
- // lockscreen)
- shadeInteractor.qsExpansion.map { it > 0f },
- ) { isGlanceableHub, isShadeVisible, qsExpansion ->
- isGlanceableHub && !(isShadeVisible || qsExpansion)
+ isAnyExpanded,
+ ) { isGlanceableHub, isAnyExpanded ->
+ isGlanceableHub && !isAnyExpanded
}
.stateIn(
scope = applicationScope,
@@ -283,9 +292,6 @@ constructor(
awaitCollapse().collect { doFadeIn ->
if (doFadeIn) {
emit(true)
- // ... and then for the animation to complete
- shadeCollapseFadeInComplete.first { it }
- shadeCollapseFadeInComplete.value = false
}
}
}
@@ -295,7 +301,7 @@ constructor(
started = SharingStarted.WhileSubscribed(),
initialValue = false,
)
- .dumpWhileCollecting("shadeCollapseFadeIn")
+ .dumpValue("shadeCollapseFadeIn")
/**
* The container occupies the entire screen, and must be positioned relative to other elements.
@@ -323,7 +329,7 @@ constructor(
// When QS expansion > 0, it should directly set the top padding so do not
// animate it
val animate = qsExpansion == 0f && !isInTransitionToAnyState
- keyguardInteractor.notificationContainerBounds.value.copy(
+ bounds.copy(
top = top,
isAnimated = animate,
)
@@ -350,6 +356,9 @@ constructor(
if (shadeExpansion > 0f || qsExpansion > 0f) {
if (configurationBasedDimensions.useSplitShade) {
emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
} else {
// Fade as QS shade expands
emit(1f - qsExpansion)
@@ -544,10 +553,6 @@ constructor(
interactor.notificationStackChanged()
}
- fun setShadeCollapseFadeInComplete(complete: Boolean) {
- shadeCollapseFadeInComplete.value = complete
- }
-
data class ConfigurationBasedDimensions(
val marginStart: Int,
val marginTop: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ab9ecab8e0c8..1faca0081155 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,6 +57,7 @@ import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -81,6 +82,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -120,6 +122,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
ShadeController shadeController,
CommandQueue commandQueue,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
MetricsLogger metricsLogger,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -147,6 +150,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mShadeController = shadeController;
mCommandQueue = commandQueue;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
mMetricsLogger = metricsLogger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -304,7 +308,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mShadeController.animateCollapseShade();
} else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
- if (mShadeViewController.isFullyCollapsed()) {
+ if (mPanelExpansionInteractor.isFullyCollapsed()) {
if (mVibrateOnOpening) {
vibrateOnNavigationKeyDown();
}
@@ -371,7 +375,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
mCameraLauncherLazy.get().launchCamera(source,
- mShadeViewController.isFullyCollapsed());
+ mPanelExpansionInteractor.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94584e3..24be3db6231f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor) {
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController) {
super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger);
+ accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
@@ -482,7 +484,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
this.mExpanded = expanded;
if (expanded) {
- removeAutoRemovalCallbacks("setExpanded(true)");
+ cancelAutoRemovalCallbacks("setExpanded(true)");
} else {
updateEntry(false /* updatePostTime */, "setExpanded(false)");
}
@@ -495,7 +497,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
mGutsShownPinned = gutsShownPinned;
if (gutsShownPinned) {
- removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
} else {
updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f29ec8f38c27..92fd90a0859a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -32,6 +32,7 @@ import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -58,6 +59,7 @@ private constructor(
private val statusBarWindowStateController: StatusBarWindowStateController,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
+ private val panelExpansionInteractor: PanelExpansionInteractor,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -218,7 +220,7 @@ private constructor(
)
return true
}
- if (shadeViewController.isFullyCollapsed && event.y < 1f) {
+ if (panelExpansionInteractor.isFullyCollapsed && event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
@@ -271,6 +273,7 @@ private constructor(
private val statusBarWindowStateController: StatusBarWindowStateController,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
+ private val panelExpansionInteractor: PanelExpansionInteractor,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -292,6 +295,7 @@ private constructor(
statusBarWindowStateController,
shadeController,
shadeViewController,
+ panelExpansionInteractor,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d4960d7a8dec..712f65df2e59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -328,7 +328,14 @@ public enum ScrimState {
GLANCEABLE_HUB_OVER_DREAM {
@Override
public void prepare(ScrimState previousState) {
- GLANCEABLE_HUB.prepare(previousState);
+ // No scrims should be visible by default in this state.
+ mBehindAlpha = 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 235ed25e2ea1..69dd50716fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -38,6 +39,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationStackScrollLayoutController mNsslController;
private final KeyguardBypassController mKeyguardBypassController;
private final HeadsUpManager mHeadsUpManager;
@@ -49,6 +51,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarWindowController statusBarWindowController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationStackScrollLayoutController nsslController,
KeyguardBypassController keyguardBypassController,
HeadsUpManager headsUpManager,
@@ -57,6 +60,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNsslController = nsslController;
mKeyguardBypassController = keyguardBypassController;
mHeadsUpManager = headsUpManager;
@@ -74,13 +78,13 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
if (inPinnedMode) {
mNotificationShadeWindowController.setHeadsUpShowing(true);
mStatusBarWindowController.setForceStatusBarVisible(true);
- if (mShadeViewController.isFullyCollapsed()) {
+ if (mPanelExpansionInteractor.isFullyCollapsed()) {
mShadeViewController.updateTouchableRegion();
}
} else {
boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- if (!mShadeViewController.isFullyCollapsed()
+ if (!mPanelExpansionInteractor.isFullyCollapsed()
|| mShadeViewController.isTracking()
|| bypassKeyguard) {
// We are currently tracking or is open and the shade doesn't need to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7dd328a4aad0..14e934fab701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -76,6 +76,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -105,6 +107,8 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import dagger.Lazy;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -116,6 +120,7 @@ import javax.inject.Inject;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.Job;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -163,6 +168,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final Lazy<ShadeController> mShadeController;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
+ private Job mListenForAlternateBouncerTransitionSteps = null;
+ private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
+
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
private boolean mTracking = false;
@@ -491,6 +499,26 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ if (mListenForAlternateBouncerTransitionSteps != null) {
+ mListenForAlternateBouncerTransitionSteps.cancel(null);
+ }
+ mListenForAlternateBouncerTransitionSteps = null;
+ if (mListenForKeyguardAuthenticatedBiometricsHandled != null) {
+ mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null);
+ }
+ mListenForKeyguardAuthenticatedBiometricsHandled = null;
+ if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow(
+ mKeyguardTransitionInteractor.transitionStepsFromState(
+ KeyguardState.ALTERNATE_BOUNCER),
+ this::consumeFromAlternateBouncerTransitionSteps
+ );
+
+ mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow(
+ mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(),
+ this::consumeKeyguardAuthenticatedBiometricsHandled
+ );
+ }
if (KeyguardWmStateRefactor.isEnabled()) {
// Show the keyguard views whenever we've told WM that the lockscreen is visible.
@@ -514,6 +542,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
+ @VisibleForTesting
+ void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+ hideAlternateBouncer(false);
+ }
+
+ /**
+ * Required without fix for b/328643370: missing AlternateBouncer (when occluded) => Gone
+ * transition.
+ */
+ @VisibleForTesting
+ void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
+ hideAlternateBouncer(false);
+ }
+ }
+
private void consumeShowStatusBarKeyguardView(boolean show) {
if (show != mLastShowing) {
if (show) {
@@ -1458,7 +1502,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
if (mAlternateBouncerInteractor.isVisibleState()) {
- hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b5ab4e3eb462..5d27467b8399 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -115,7 +115,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
private final LockPatternUtils mLockPatternUtils;
private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
@@ -126,7 +126,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarNotificationActivityStarterLogger mLogger;
private final NotificationPresenter mPresenter;
- private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ActivityTransitionAnimator mActivityTransitionAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -163,7 +163,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarNotificationActivityStarterLogger logger,
OnUserInteractionCallback onUserInteractionCallback,
NotificationPresenter presenter,
- ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationShadeWindowController notificationShadeWindowController,
ActivityTransitionAnimator activityTransitionAnimator,
ShadeAnimationInteractor shadeAnimationInteractor,
@@ -192,13 +192,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLockPatternUtils = lockPatternUtils;
mStatusBarRemoteInputCallback = remoteInputCallback;
mActivityIntentHelper = activityIntentHelper;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationShadeWindowController = notificationShadeWindowController;
mShadeAnimationInteractor = shadeAnimationInteractor;
mMetricsLogger = metricsLogger;
mLogger = logger;
mOnUserInteractionCallback = onUserInteractionCallback;
mPresenter = presenter;
- mShadeViewController = shadeViewController;
mActivityTransitionAnimator = activityTransitionAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
mPowerInteractor = powerInteractor;
@@ -296,7 +296,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
// Always defer the keyguard dismiss when animating.
- return animate || !mShadeViewController.isFullyCollapsed();
+ return animate || !mPanelExpansionInteractor.isFullyCollapsed();
}
private void handleNotificationClickAfterPanelCollapsed(
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 8e9c0384987d..5a3ae73555a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -44,6 +44,7 @@ import com.android.systemui.res.R;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,6 +88,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
private final ShadeViewController mNotificationPanel;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
@@ -108,6 +110,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
StatusBarNotificationPresenter(
Context context,
ShadeViewController panel,
+ PanelExpansionInteractor panelExpansionInteractor,
QuickSettingsController quickSettingsController,
HeadsUpManager headsUp,
NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
@Override
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing()
+ return mPanelExpansionInteractor.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
}
@@ -232,7 +236,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
@Override
public boolean isPresenterFullyCollapsed() {
- return mNotificationPanel.isFullyCollapsed();
+ return mPanelExpansionInteractor.isFullyCollapsed();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 000000000000..6aaf5d60ac0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+ private val tag = "AvalancheController"
+ private val debug = false
+
+ // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+ @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+ // List of runnables to run for the HUN showing right now
+ private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+ // HeadsUpEntry waiting to show
+ // Use sortable list instead of priority queue for debugging
+ private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+ // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+ // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+ // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+ @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+ // Map of Runnable to label for debugging only
+ private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+ // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+ // For debugging only
+ @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+ /**
+ * Run or delay Runnable for given HeadsUpEntry
+ */
+ fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+ if (debug) {
+ debugRunnableLabelMap[runnable] = label
+ }
+
+ if (isShowing(entry)) {
+ log {"$fn => [update showing]" }
+ runnable.run()
+ } else if (entry in nextMap) {
+ log { "$fn => [update next]" }
+ nextMap[entry]?.add(runnable)
+ } else if (headsUpEntryShowing == null) {
+ log { "$fn => [showNow]" }
+ showNow(entry, arrayListOf(runnable))
+ } else {
+ // Clean up invalid state when entry is in list but not map and vice versa
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+
+ addToNext(entry, runnable)
+
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ }
+ }
+ logState("after $fn")
+ }
+
+ @VisibleForTesting
+ fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+ nextMap[entry] = arrayListOf(runnable)
+ nextList.add(entry)
+ }
+
+ /**
+ * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+ * all Runnables associated with that entry.
+ */
+ fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+ if (entry in nextMap) {
+ log { "$fn => [remove from next]" }
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+ } else if (entry in debugDropSet) {
+ log { "$fn => [remove from dropset]" }
+ debugDropSet.remove(entry)
+ } else if (isShowing(entry)) {
+ log { "$fn => [remove showing ${getKey(entry)}]" }
+ runnable.run()
+ showNext()
+ } else {
+ log { "$fn => [removing untracked ${getKey(entry)}]" }
+ }
+ logState("after $fn")
+ }
+
+ /**
+ * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
+ * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
+ * avalanche.
+ */
+ fun shortenDuration(entry: HeadsUpEntry): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ // Use default display duration, like we always did before AvalancheController existed
+ return false
+ }
+ val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+ headsUpEntryShowing?.let { showingList.add(it) }
+ val allEntryList = showingList + nextList
+
+ // Shorten duration if not last entry
+ return allEntryList.indexOf(entry) != allEntryList.size - 1
+ }
+
+ /**
+ * Return true if entry is waiting to show.
+ */
+ fun isWaiting(key: String): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ return false
+ }
+ for (entry in nextMap.keys) {
+ if (entry.mEntry?.key.equals(key)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Return list of keys for huns waiting
+ */
+ fun getWaitingKeys(): MutableList<String> {
+ if (!NotificationThrottleHun.isEnabled) {
+ return mutableListOf()
+ }
+ val keyList = mutableListOf<String>()
+ for (entry in nextMap.keys) {
+ entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+ }
+ return keyList
+ }
+
+ private fun isShowing(entry: HeadsUpEntry): Boolean {
+ return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+ }
+
+ private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+ log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+ headsUpEntryShowing = entry
+
+ runnableList.forEach {
+ if (it in debugRunnableLabelMap) {
+ log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ }
+ it.run()
+ }
+ }
+
+ private fun showNext() {
+ log { "showNext" }
+ headsUpEntryShowing = null
+
+ if (nextList.isEmpty()) {
+ log { "no more to show!" }
+ return
+ }
+
+ // Only show first (top priority) entry in next batch
+ nextList.sort()
+ headsUpEntryShowing = nextList[0]
+ headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+ // Remove runnable labels for dropped huns
+ val listToDrop = nextList.subList(1, nextList.size)
+ if (debug) {
+ // Clear runnable labels
+ for (e in listToDrop) {
+ val runnableList = nextMap[e]!!
+ for (r in runnableList) {
+ debugRunnableLabelMap.remove(r)
+ }
+ }
+ debugDropSet.addAll(listToDrop)
+ }
+
+ clearNext()
+ showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+ }
+
+ fun clearNext() {
+ nextList.clear()
+ nextMap.clear()
+ }
+
+ // Methods below are for logging only ==========================================================
+
+ private inline fun log(s: () -> String) {
+ if (debug) {
+ Log.d(tag, s())
+ }
+ }
+
+ // TODO(b/315362456) expose as dumpable for bugreports
+ private fun logState(reason: String) {
+ log { "state $reason" }
+ log { "showing: " + getKey(headsUpEntryShowing) }
+ log { "next list: $nextListStr map: $nextMapStr" }
+ log { "drop: $dropSetStr" }
+ }
+
+ private val dropSetStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in debugDropSet) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextListStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextList) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextMapStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextMap.keys) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ fun getKey(entry: HeadsUpEntry?): String {
+ if (entry == null) {
+ return "null"
+ }
+ if (entry.mEntry == null) {
+ return entry.toString()
+ }
+ return entry.mEntry!!.key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c83802..05cc73edd892 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@ import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -68,6 +70,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
+ private final AvalancheController mAvalancheController;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
+ mAvalancheController = avalancheController;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
-
- // Add new entry and begin managing it
HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
- entry.setInterruption();
+ Runnable runnable = () -> {
+ // TODO(b/315362456) log outside runnable too
+ mLogger.logShowNotification(entry);
+
+ // Add new entry and begin managing it
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
+ updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+ entry.setInterruption();
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
/**
@@ -181,6 +194,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
mLogger.logRemoveNotification(key, releaseImmediately);
+
+ if (mAvalancheController.isWaiting(key)) {
+ removeEntry(key);
+ return true;
+ }
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
@@ -203,6 +221,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ Runnable runnable = () -> {
+ updateNotificationInternal(key, shouldHeadsUpAgain);
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+ }
+
+ private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
for (String key : keysToRemove) {
removeEntry(key);
}
+ for (String key : mAvalancheController.getWaitingKeys()) {
+ removeEntry(key);
+ }
}
/**
* Returns the entry if it is managed by this manager.
* @param key key of notification
* @return the entry
+ * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
*/
@Nullable
public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@NonNull
@Override
public Stream<NotificationEntry> getAllEntries() {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -267,7 +298,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* @return true if the notification is managed by this manager
*/
public boolean isHeadsUpEntry(@NonNull String key) {
- return mHeadsUpEntryMap.containsKey(key);
+ return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
}
/**
@@ -331,7 +362,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ void onEntryAdded(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
@@ -349,20 +380,24 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- if (headsUpEntry == null) {
- return;
- }
- NotificationEntry entry = headsUpEntry.mEntry;
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ Runnable runnable = () -> {
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
/**
@@ -380,7 +415,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
@@ -416,7 +451,9 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mHeadsUpEntryMap.keySet()) {
+ List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+ keySet.addAll(mAvalancheController.getWaitingKeys());
+ for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
@@ -515,18 +553,22 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
- HeadsUpEntry entry = getHeadsUpEntry(key);
- setEntryPinned(entry, false /* isPinned */);
- // maybe it got un sticky
- entry.updateEntry(false /* updatePostTime */, "unpinAll");
-
- // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
- // on the screen.
- if (userUnPinned && entry.mEntry != null) {
- if (entry.mEntry.mustStayOnScreen()) {
- entry.mEntry.setHeadsUpIsVisible();
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+
+ Runnable runnable = () -> {
+ setEntryPinned(headsUpEntry, false /* isPinned */);
+ // maybe it got un sticky
+ headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+ // 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.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
+ }
}
- }
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
}
}
@@ -606,6 +648,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
@Override
public boolean isSticky(String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry != null) {
return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
- * lifecycle automatically when created.
+ * lifecycle automatically when created. This class is public because it is exposed by methods
+ * of AvalancheController that take it as param.
*/
- protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -672,27 +716,41 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
/**
+ * An interface that returns the amount of time left this HUN should show.
+ */
+ interface FinishTimeUpdater {
+ long updateAndGetTimeRemaining();
+ }
+
+ /**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+ Runnable runnable = () -> {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+ };
+ mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
+ cancelAutoRemovalCallbacks("updateEntry (sticky)");
return;
}
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long finishTime = calculateFinishTime();
+ final long now = mSystemClock.elapsedRealtime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
}
/**
@@ -758,12 +816,31 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
+ @Override
+ public int hashCode() {
+ if (mEntry == null) return super.hashCode();
+ int result = mEntry.getKey().hashCode();
+ result = 31 * result;
+ return result;
+ }
+
+ @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 (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+ return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+ }
+ return false;
+ }
+
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks("reset()");
+ cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
mExpanded = false;
@@ -773,37 +850,48 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/**
* Clear any pending removal runnables.
*/
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ Runnable runnable = () -> {
+ final boolean removed = cancelAutoRemovalCallbackInternal();
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " removeAutoRemovalCallbacks");
}
- public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
+ public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ @NonNull String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ Runnable runnable = () -> {
+ long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
- mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
- delayMillis);
+ final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMs);
+
+ if (deletedExistingRemovalRunnable) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " scheduleAutoRemovalCallback");
}
- public boolean removeAutoRemovalCallbackInternal() {
+ public boolean cancelAutoRemovalCallbackInternal() {
final boolean scheduled = (mCancelRemoveRunnable != null);
if (scheduled) {
- mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue
mCancelRemoveRunnable = null;
}
@@ -815,8 +903,12 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
@@ -834,9 +926,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* {@link SystemClock#elapsedRealtime()}
*/
protected long calculateFinishTime() {
- final long duration = getRecommendedHeadsUpTimeoutMs(
- isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+ int requestedTimeOutMs;
+ if (isStickyForSomeTime()) {
+ requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+ } else if (mAvalancheController.shortenDuration(this)) {
+ requestedTimeOutMs = 1000;
+ } else {
+ requestedTimeOutMs = mAutoDismissTime;
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
return mPostTime + duration;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831c5964..d10554f9c254 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -32,7 +32,7 @@ import kotlinx.coroutines.plus
private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
-/** Providers for various SystemIU specific coroutines-related constructs. */
+/** Providers for various SystemUI-specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e53b5e2..1bceee9b2d34 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@ sealed interface MediaDeviceSession {
/** Current media state is unknown yet. */
data object Unknown : MediaDeviceSession
}
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+ this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 37661b53c98a..d49cb1ea6958 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -110,9 +111,6 @@ constructor(
null,
)
- private fun MediaDeviceSession.isPlaying(): Boolean =
- this is MediaDeviceSession.Active && playbackState?.isActive == true
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
index 71bce5e470f4..9d74c581f866 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -18,11 +18,12 @@ package com.android.systemui.volume.panel.component.spatial.domain
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
@VolumePanelScope
class SpatialAudioAvailabilityCriteria
@@ -31,5 +32,11 @@ constructor(private val interactor: SpatialAudioComponentInteractor) :
ComponentAvailabilityCriteria {
override fun isAvailable(): Flow<Boolean> =
- interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+ combine(interactor.isAvailable, interactor.isEnabled) { isAvailable, isEnabled ->
+ if (isAvailable is SpatialAudioAvailabilityModel.SpatialAudio) {
+ isEnabled !is SpatialAudioEnabledModel.Unknown
+ } else {
+ false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 6032bfe3b50a..298ca67d92a8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -18,8 +18,9 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,12 +55,9 @@ constructor(
private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
mediaOutputInteractor.currentConnectedDevice
.map { mediaDevice ->
- mediaDevice ?: return@map null
- val btDevice: CachedBluetoothDevice =
- (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
- btDevice.getAudioDeviceAttributes()
+ if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
}
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
/**
* Returns spatial audio availability model. It can be:
@@ -115,7 +113,7 @@ constructor(
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- SpatialAudioEnabledModel.Disabled,
+ SpatialAudioEnabledModel.Unknown,
)
/**
@@ -137,34 +135,50 @@ constructor(
changes.emit(Unit)
}
- private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
- return listOf(
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_BROADCAST,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_HEARING_AID,
- address
- )
+ private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+ when (this) {
+ is PhoneMediaDevice -> return builtinSpeaker
+ is BluetoothMediaDevice -> {
+ val device = cachedDevice ?: return null
+ return listOf(
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ device.address,
+ )
+ )
+ .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+ }
+ else -> return null
+ }
+ }
+
+ private companion object {
+ val builtinSpeaker =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ ""
)
- .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 9735e5cbd9c9..4255a4044737 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -39,4 +39,7 @@ interface SpatialAudioEnabledModel {
/** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
data object HeadTrackingEnabled : SpatialAudioEnabled
+
+ /** Spatial audio enabled state is unknown. */
+ data object Unknown : SpatialAudioEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf60f1b..ecd89eab1d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -24,75 +24,24 @@ import javax.inject.Inject
class VolumeSliderInteractor @Inject constructor() {
/** mimic percentage volume setting */
- val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+ private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
/**
* Translates [volume], that belongs to [volumeRange] to the value that belongs to
* [displayValueRange].
- *
- * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
- * translates to the same volume as [volume] parameter. This ensures smooth slider experience
- * (avoids snapping when the user stops dragging).
*/
fun processVolumeToValue(
volume: Int,
volumeRange: ClosedRange<Int>,
- currentValue: Float?,
- isMuted: Boolean,
): Float {
- if (isMuted) {
- return 0f
- }
- val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
- return if (volume != volumeRange.start && volume == changedVolume) {
- currentValue
- } else {
- translateToRange(
- currentValue = volume.toFloat(),
- currentRangeStart = volumeRange.start.toFloat(),
- currentRangeEnd = volumeRange.endInclusive.toFloat(),
- targetRangeStart = displayValueRange.start,
- targetRangeEnd = displayValueRange.endInclusive,
- )
- }
- }
-
- /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
- fun translateValueToVolume(
- value: Float,
- volumeRange: ClosedRange<Int>,
- ): Int {
- return translateToRange(
- currentValue = value,
- currentRangeStart = displayValueRange.start,
- currentRangeEnd = displayValueRange.endInclusive,
- targetRangeStart = volumeRange.start.toFloat(),
- targetRangeEnd = volumeRange.endInclusive.toFloat(),
- )
- .toInt()
- }
-
- /**
- * Translates a value from one range to another.
- *
- * ```
- * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
- * Result: 37.5
- * ```
- */
- private fun translateToRange(
- currentValue: Float,
- currentRangeStart: Float,
- currentRangeEnd: Float,
- targetRangeStart: Float,
- targetRangeEnd: Float,
- ): Float {
- val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
- val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+ val currentRangeStart: Float = volumeRange.start.toFloat()
+ val targetRangeStart: Float = displayValueRange.start
+ val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
+ val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
if (currentRangeLength == 0f || targetRangeLength == 0f) {
return 0f
}
- val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+ val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
return targetRangeStart + volumeFraction * targetRangeLength
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index faf743475579..1b732081a12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,12 +21,14 @@ import android.media.AudioManager
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -54,14 +56,6 @@ constructor(
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
)
- private val mutedIconsByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
- )
private val labelsByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,59 +68,82 @@ constructor(
mapOf(
AudioStream(AudioManager.STREAM_NOTIFICATION) to
R.string.stream_notification_unavailable,
+ AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+ AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
- private var value = 0f
override val slider: StateFlow<SliderState> =
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
- ) { model, isEnabled ->
- model.toState(value, isEnabled)
+ audioVolumeInteractor.ringerMode,
+ ) { model, isEnabled, ringerMode ->
+ model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
audioViewModel ?: return
coroutineScope.launch {
- value = newValue
- val volume =
- volumeSliderInteractor.translateValueToVolume(
- newValue,
- audioViewModel.audioStreamModel.volumeRange
- )
- audioVolumeInteractor.setVolume(audioStream, volume)
+ audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
}
}
- private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+ override fun toggleMuted(state: SliderState) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ coroutineScope.launch {
+ audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
+ }
+ }
+
+ private fun AudioStreamModel.toState(
+ isEnabled: Boolean,
+ ringerMode: RingerMode,
+ ): State {
return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume,
- volumeRange,
- value,
- isMuted,
+ value = volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
),
- valueRange = volumeSliderInteractor.displayValueRange,
- icon = getIcon(this),
+ icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
isEnabled = isEnabled,
+ a11yStep = volumeRange.step,
audioStreamModel = this,
)
}
- private fun getIcon(model: AudioStreamModel): Icon {
- val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+ private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+ val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
if (isMutedOrNoVolume) {
- mutedIconsByStream
+ when (audioStream.value) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+ AudioManager.STREAM_RING ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_NOTIFICATION ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+ else -> null
+ }
} else {
- iconsByStream
- }[audioStream]
+ iconsByStream[audioStream]
+ }
?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
@@ -139,8 +156,10 @@ constructor(
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
override val label: String,
+ override val valueText: String,
override val disabledMessage: String?,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
val audioStreamModel: AudioStreamModel,
) : SliderState
@@ -148,8 +167,10 @@ constructor(
override val value: Float = 0f
override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
override val icon: Icon? = null
+ override val valueText: String = ""
override val label: String = ""
override val disabledMessage: String? = null
+ override val a11yStep: Int = 0
override val isEnabled: Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826c0c17..86b2d73de3e3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,8 +26,8 @@ import com.android.systemui.volume.panel.component.volume.domain.interactor.Volu
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -46,46 +46,46 @@ constructor(
) : SliderViewModel {
private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
- private val value = MutableStateFlow(0f)
override val slider: StateFlow<SliderState> =
- combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
- getCurrentState(value)
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+ combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- value.value = newValue
- castVolumeInteractor.setVolume(
- routingSession,
- volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
- )
+ castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
}
}
- private fun getCurrentState(value: Float): State {
- return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume = routingSession.routingSessionInfo.volume,
- volumeRange = volumeRange,
- currentValue = value,
- isMuted = false,
- ),
- valueRange = volumeSliderInteractor.displayValueRange,
+ override fun toggleMuted(state: SliderState) {
+ // do nothing because this action isn't supported for Cast sliders.
+ }
+
+ private fun getCurrentState(): State =
+ State(
+ value = routingSession.routingSessionInfo.volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = Icon.Resource(R.drawable.ic_cast, null),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(
+ volume = routingSession.routingSessionInfo.volume,
+ volumeRange = volumeRange,
+ )
+ ),
label = context.getString(R.string.media_device_cast),
isEnabled = true,
+ a11yStep = 1
)
- }
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
+ override val valueText: String,
override val label: String,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
) : SliderState {
override val disabledMessage: String?
get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 6e9794b02143..b87d0a786740 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,7 +27,13 @@ sealed interface SliderState {
val value: Float
val valueRange: ClosedFloatingPointRange<Float>
val icon: Icon?
+ val isEnabled: Boolean
+ val valueText: String
val label: String
+ /**
+ * A11y slider controls works by adjusting one step up or down. The default slider step isn't
+ * enough to trigger rounding to the correct value.
+ */
+ val a11yStep: Int
val disabledMessage: String?
- val isEnabled: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b3222e34d..e78f833086f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,12 @@ interface SliderViewModel {
val slider: StateFlow<SliderState>
- fun onValueChangeFinished(state: SliderState, newValue: Float)
+ fun onValueChanged(state: SliderState, newValue: Float)
+
+ fun toggleMuted(state: SliderState)
+
+ companion object {
+
+ fun formatValue(value: Float): String = "%.0f".format(value)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323775d3..aaee24b9357f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@ package com.android.systemui.volume.panel.component.volume.ui.viewmodel
import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
/**
* Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@ class AudioVolumeComponentViewModel
constructor(
@VolumePanelScope private val scope: CoroutineScope,
castVolumeInteractor: CastVolumeInteractor,
+ mediaOutputInteractor: MediaOutputInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
) {
@@ -90,4 +98,17 @@ constructor(
remoteSessionsViewModels + streamViewModels
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+ val isExpanded: StateFlow<Boolean> =
+ merge(
+ mutableIsExpanded.onStart { emit(false) },
+ mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ fun onExpandedChanged(isExpanded: Boolean) {
+ scope.launch { mutableIsExpanded.emit(isExpanded) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a2063a..635191a1793a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@ constructor(
!footerComponents.contains(it.key) &&
it.key != bottomBar
}
- val headerComponents = components.filter { headerComponents.contains(it.key) }
- val footerComponents = components.filter { footerComponents.contains(it.key) }
+ val headerComponents =
+ components
+ .filter { it.key in headerComponents }
+ .sortedBy { headerComponents.indexOf(it.key) }
+ val footerComponents =
+ components
+ .filter { it.key in footerComponents }
+ .sortedBy { footerComponents.indexOf(it.key) }
return ComponentsLayout(
- headerComponents = headerComponents.sortedBy { it.key },
+ headerComponents = headerComponents,
contentComponents = contentComponents.sortedBy { it.key },
- footerComponents = footerComponents.sortedBy { it.key },
+ footerComponents = footerComponents,
bottomBarComponent = components.find { it.key == bottomBar }
?: error(
"VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index e893eb196039..11fe86277903 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -30,12 +30,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.animation.AnimatorTestRule;
+import android.platform.test.annotations.DisableFlags;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.Flags;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.plugins.clocks.ClockConfig;
import com.android.systemui.plugins.clocks.ClockController;
@@ -80,6 +82,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
public void onLocaleListChangedNotifiesClockSwitchController() {
ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
ArgumentCaptor.forClass(ConfigurationListener.class);
@@ -239,6 +242,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
public void statusAreaHeightChange_animatesHeightOutputChange() {
// Init & Capture Layout Listener
mController.onInit();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 976cd5bd21c4..1f7d0336c590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -90,12 +90,12 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
- doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(
mContext, stubWindowManager, mAccessibilityManager,
stubMenuViewModel, stubMenuViewAppearance, mMenuView,
mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+ doNothing().when(mMenuViewLayer).gotoEditScreen();
doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
mStubListView = new RecyclerView(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a744129..de696f4b9a2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Insets;
@@ -136,6 +138,8 @@ public class MenuViewLayerTest extends SysuiTestCase {
private WindowManager mStubWindowManager;
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ @Mock
+ private PackageManager mMockPackageManager;
private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
@@ -162,14 +166,15 @@ public class MenuViewLayerTest extends SysuiTestCase {
new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
// Ensure tests don't actually update metrics.
doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
- doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
mFloatingMenu, mSecureSettings));
- mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
+ doNothing().when(mSpyContext).startActivity(any());
+ when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+
mLastAccessibilityButtonTargets =
Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -277,9 +282,31 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void onEditAction_gotoEditScreen_isCalled() {
+ public void onEditAction_startsActivity() {
+ mockActivityQuery(true);
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
- verify(mMenuView).gotoEditScreen();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mSpyContext).startActivity(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+ mMenuViewLayer.getIntentForEditScreen().getAction());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onEditAction_noResolve_doesNotStart() {
+ mockActivityQuery(false);
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+ verify(mSpyContext, never()).startActivity(any());
+ }
+
+ @Test
+ public void getIntentForEditScreen_validate() {
+ Intent intent = mMenuViewLayer.getIntentForEditScreen();
+ String[] targets = intent.getBundleExtra(
+ ":settings:show_fragment_args").getStringArray("targets");
+
+ assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
}
@Test
@@ -308,20 +335,6 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchShowingImeInsets();
-
- final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(menuBottom).isLessThan(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +350,6 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchHidingImeInsets();
-
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -527,4 +527,15 @@ public class MenuViewLayerTest extends SysuiTestCase {
magnetListener.onReleasedInTarget(
new MagnetizedObject.MagneticTarget(view, 200), mock(MagnetizedObject.class));
}
+
+ private void mockActivityQuery(boolean successfulQuery) {
+ // Query just needs to return a non-empty set to be successful.
+ ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+ if (successfulQuery) {
+ resolveInfos.add(new ResolveInfo());
+ }
+ when(mMockPackageManager.queryIntentActivities(
+ any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index eced4651aeb6..f6288b633ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,19 +22,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.UiModeManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -58,8 +52,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-
/** Tests for {@link MenuView}. */
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -79,8 +71,6 @@ public class MenuViewTest extends SysuiTestCase {
private AccessibilityManager mAccessibilityManager;
private SysuiTestableContext mSpyContext;
- @Mock
- private PackageManager mMockPackageManager;
@Before
public void setUp() throws Exception {
@@ -91,7 +81,6 @@ public class MenuViewTest extends SysuiTestCase {
mSpyContext = spy(mContext);
doNothing().when(mSpyContext).startActivity(any());
- when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
secureSettings);
@@ -178,32 +167,6 @@ public class MenuViewTest extends SysuiTestCase {
assertThat(radiiAnimator.isStarted()).isTrue();
}
- @Test
- public void getIntentForEditScreen_validate() {
- Intent intent = mMenuView.getIntentForEditScreen();
- String[] targets = intent.getBundleExtra(
- ":settings:show_fragment_args").getStringArray("targets");
-
- assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
- assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void gotoEditScreen_sendsIntent() {
- mockActivityQuery(true);
- mMenuView.gotoEditScreen();
- verify(mSpyContext).startActivity(any());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void gotoEditScreen_noResolve_doesNotStart() {
- mockActivityQuery(false);
- mMenuView.gotoEditScreen();
- verify(mSpyContext, never()).startActivity(any());
- }
-
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
@@ -226,14 +189,4 @@ public class MenuViewTest extends SysuiTestCase {
mUiModeManager.setNightMode(mNightMode);
Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
}
-
- private void mockActivityQuery(boolean successfulQuery) {
- // Query just needs to return a non-empty set to be successful.
- ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
- if (successfulQuery) {
- resolveInfos.add(new ResolveInfo());
- }
- when(mMockPackageManager.queryIntentActivities(
- any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea9fd31..2b4e9ec4a017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -20,7 +20,6 @@ import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
@@ -386,7 +385,6 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testShowCredentialUI_withDescription() {
- mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
@@ -397,6 +395,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/302735104")
fun testShowCredentialUI_withCustomBp() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 7b972d3707af..81d4e8302c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -135,6 +137,8 @@ class PromptRepositoryImplTest : SysuiTestCase() {
@Test
fun showBpWithoutIconForCredential_withCustomBp() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
for (case in
listOf(
PromptKind.Biometric(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 140849b8e257..7db4ca966890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -35,6 +35,7 @@ import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags.FLAG_BP_TALKBACK
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
@@ -1256,6 +1257,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1267,6 +1269,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1278,6 +1281,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun logoIsNullIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@@ -1285,6 +1289,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1293,6 +1298,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1301,6 +1307,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@@ -1309,6 +1316,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun logoDescriptionIsEmptyIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@@ -1316,6 +1324,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@@ -1324,10 +1333,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun logoDescriptionSetByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
+ @Test
+ fun iconViewLoaded() = runGenericTest {
+ val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+ // TODO(b/328677869): Add test for noIcon logic.
+ assertThat(isIconViewLoaded).isFalse()
+
+ viewModel.setIsIconViewLoaded(true)
+
+ assertThat(isIconViewLoaded).isTrue()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index e7963031411d..701b7039a1ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -401,7 +401,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -439,7 +439,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -481,7 +481,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033217b3..45d20dcd9d11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -188,6 +188,20 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
}
@Test
+ public void testRegisterSensor_OccludingActivity() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e1e9fcb2d059..dac88a340cb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -532,6 +532,18 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
}
+ @Test
+ fun lockedOut_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut)
+ }
+
+ @Test
+ fun authenticated_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated)
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
new file mode 100644
index 000000000000..decbdaf0feee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+ testScope.runTest {
+ val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ fingerprintAuthRepository.setLockedOut(true)
+
+ assertThat(currentlyAllowed).isFalse()
+
+ fingerprintAuthRepository.setLockedOut(false)
+ assertThat(currentlyAllowed).isTrue()
+
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+ assertThat(currentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun isSensorUnderDisplay_trueForUdfpsSensorTypes() =
+ testScope.runTest {
+ val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay)
+
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isSensorUnderDisplay).isTrue()
+
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+
+ fingerprintPropertyRepository.supportsSideFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ad80a06e3fcd..8700001fddd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -27,7 +27,9 @@ import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -37,6 +39,7 @@ import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.UUID
+import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -224,6 +227,101 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
+ fun startingSecondManualTransitionWillCancelPreviousManualTransition() =
+ TestScope().runTest {
+ // Drop initial steps from OFF which are sent in the constructor
+ val steps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.transitions
+ .dropWhile { step -> step.from == OFF }
+ .onEach { steps.add(it) }
+ .launchIn(this)
+
+ val firstUuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(firstUuid)
+ underTest.updateTransition(firstUuid, 0.5f, TransitionState.RUNNING)
+ runCurrent()
+
+ val secondUuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, DREAMING, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(secondUuid)
+ underTest.updateTransition(secondUuid, 0.7f, TransitionState.RUNNING)
+ // Trying to transition the old uuid should be ignored.
+ underTest.updateTransition(firstUuid, 0.6f, TransitionState.RUNNING)
+ runCurrent()
+
+ assertThat(steps)
+ .containsExactly(
+ TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+ TransitionStep(AOD, DREAMING, 0.5f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, DREAMING, 0.7f, TransitionState.RUNNING, OWNER_NAME),
+ )
+ .inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun startingSecondTransitionWillCancelPreviousManualTransition() =
+ TestScope().runTest {
+ // Drop initial steps from OFF which are sent in the constructor
+ val steps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.transitions
+ .dropWhile { step -> step.from == OFF }
+ .onEach { steps.add(it) }
+ .launchIn(this)
+
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(uuid)
+ underTest.updateTransition(uuid, 0.5f, TransitionState.RUNNING)
+ runCurrent()
+
+ // Start new transition to dreaming, should cancel previous one.
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ AOD,
+ DREAMING,
+ getAnimator(),
+ TransitionModeOnCanceled.RESET,
+ ),
+ )
+ runCurrent()
+
+ // Trying to transition the old uuid should be ignored.
+ underTest.updateTransition(uuid, 0.6f, TransitionState.RUNNING)
+ runCurrent()
+
+ assertThat(steps.take(3))
+ .containsExactly(
+ TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+ )
+ .inOrder()
+
+ job.cancel()
+ }
+
+ @Test
fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() {
underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
assertThat(wtfHandler.failed).isTrue()
@@ -336,6 +434,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
private class WtfHandler : TerribleFailureHandler {
var failed = false
+
override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
failed = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 87391cce9136..d410dac1b2e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +37,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
@ExperimentalCoroutinesApi
@RunWith(JUnit4::class)
@@ -48,6 +50,20 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
private val underTest = kosmos.alternateBouncerViewModel
@Test
+ fun showPrimaryBouncer() =
+ testScope.runTest {
+ underTest.showPrimaryBouncer()
+ verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
+ }
+
+ @Test
+ fun hideAlternateBouncer() =
+ testScope.runTest {
+ underTest.hideAlternateBouncer()
+ verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
+ }
+
+ @Test
fun transitionToAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index bcec6109faf6..b80dcd41d53e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlin.math.min
+import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@ import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -134,7 +134,12 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel
@Mock
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+ LockscreenToGlanceableHubTransitionViewModel
+ @Mock
+ private lateinit var glanceableHubToLockscreenTransitionViewModel:
+ GlanceableHubToLockscreenTransitionViewModel
+ @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
@@ -271,6 +276,10 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
+ whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
+ whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
whenever(transitionInteractor.finishedKeyguardState)
.thenReturn(intendedFinishedKeyguardStateFlow)
@@ -307,6 +316,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel =
+ glanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel =
@@ -316,6 +327,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel =
+ lockscreenToGlanceableHubTransitionViewModel,
transitionInteractor = transitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2a46a3..b38d5e326b97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@ import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
@@ -580,6 +581,7 @@ public class NavigationBarTest extends SysuiTestCase {
() -> Optional.of(mCentralSurfaces),
mKeyguardStateController,
mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89a1514..0101741a9242 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -100,6 +101,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
Configuration mConfiguration;
@Mock
Runnable mHorizontalLayoutListener;
+ @Mock
+ VibratorHelper mVibratorHelper;
private QSPanelControllerBase<QSPanel> mController;
@@ -110,7 +113,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+ qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+ mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 85d7d9865c7c..916e8ddb6e8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val sceneContainerFlags = FakeSceneContainerFlags()
@@ -101,6 +103,7 @@ class QSPanelControllerTest : SysuiTestCase() {
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
sceneContainerFlags,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c1430844d12..71a9a8b3318f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
import org.junit.After
@@ -59,6 +60,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var tile: QSTile
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
+ dumpManager,
+ vibratorHelper,
)
controller.init()
@@ -157,7 +160,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ vibratorHelper: VibratorHelper,
) :
QuickQSPanelController(
view,
@@ -170,7 +174,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
uiEventLogger,
qsLogger,
dumpManager,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ vibratorHelper,
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 3122edb8f50e..761c411bdcb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
@@ -66,6 +67,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
+ @Mock private lateinit var panelInteractor: PanelInteractor
@Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@@ -96,6 +98,7 @@ class RecordIssueTileTest : SysuiTestCase() {
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
+ panelInteractor,
userContextProvider,
delegateFactory,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
index 8986d99712a0..cd1452a6bf84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
@@ -68,7 +68,7 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() {
fun testGetValue_valueUnset() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
runCurrent()
@@ -81,7 +81,7 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() {
fun testGetValue_valueFalse() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM)
runCurrent()
@@ -94,7 +94,7 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() {
fun testGetValue_valueTrue() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM)
runCurrent()
@@ -104,15 +104,16 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() {
}
@Test
- fun testGetValue_valueTrue_secondaryUser_returnUnset() {
+ fun testGetValue_valueTrue_secondaryUser_returnTrue() {
testScope.runTest {
userRepository.setSelectedUserInfo(SECONDARY_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
+ secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID)
secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID)
runCurrent()
- assertThat(actualValue).isEqualTo(UNSET)
+ assertThat(actualValue).isEqualTo(ENABLED)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index ab90b9be3c1e..25ba09a0ce90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -25,6 +25,7 @@ import com.android.settingslib.RestrictedLockUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
@@ -66,6 +67,8 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
private lateinit var listener: ToggleSlider.Listener
@Mock
private lateinit var vibratorHelper: VibratorHelper
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -91,6 +94,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
mFalsingManager,
uiEventLogger,
SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+ activityStarter,
)
mController.init()
mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
@Test
fun testEnforceAdminRelayed() {
mController.setEnforcedAdmin(enforcedAdmin)
- verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin)
+ verify(brightnessSliderView).setAdminBlocker(notNull())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 617b25d97eee..88b239a77433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -481,8 +481,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
// AND status bar doesn't want it
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
.thenReturn(false)
- // AND shade is not fully expanded
- whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND shade is not fully expanded (mock is false by default)
// AND the lock icon does NOT want the touch
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
// AND quick settings controller DOES want it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 3defee9e6eb5..722387c79536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,7 +28,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
-
+import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
@@ -50,6 +50,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
+@FlakyTest(bugId = 327655994) // Also b/324682425
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PluginInstanceTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index b548117684ad..e90a3ac8bfdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
@@ -157,52 +158,52 @@ public final class MediaCoordinatorTest extends SysuiTestCase {
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
mListener.onEntryInit(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager).updateIcons(eq(mMediaEntry));
+ verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
@@ -211,40 +212,40 @@ public final class MediaCoordinatorTest extends SysuiTestCase {
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
@@ -253,19 +254,19 @@ public final class MediaCoordinatorTest extends SysuiTestCase {
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index a12806b9cc99..4ac9dc2be161 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.icon
import android.app.ActivityManager
@@ -38,6 +40,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +75,11 @@ class IconManagerTest : SysuiTestCase() {
@Mock private lateinit var notifCollection: CommonNotifCollection
@Mock private lateinit var launcherApps: LauncherApps
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val mainContext = testScope.coroutineContext
+ private val bgContext = testScope.backgroundScope.coroutineContext
+
private val iconBuilder = IconBuilder(context)
private lateinit var iconManager: IconManager
@@ -85,7 +96,15 @@ class IconManagerTest : SysuiTestCase() {
`when`(shortcut.icon).thenReturn(shortcutIc)
`when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
- iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+ iconManager =
+ IconManager(
+ notifCollection,
+ launcherApps,
+ iconBuilder,
+ testScope,
+ bgContext,
+ mainContext,
+ )
}
@Test
@@ -94,6 +113,7 @@ class IconManagerTest : SysuiTestCase() {
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
}
@@ -103,6 +123,7 @@ class IconManagerTest : SysuiTestCase() {
notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
}
@@ -116,6 +137,7 @@ class IconManagerTest : SysuiTestCase() {
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
}
@@ -129,6 +151,7 @@ class IconManagerTest : SysuiTestCase() {
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -143,6 +166,7 @@ class IconManagerTest : SysuiTestCase() {
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -161,6 +185,7 @@ class IconManagerTest : SysuiTestCase() {
entry?.setSensitive(true, true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -175,6 +200,7 @@ class IconManagerTest : SysuiTestCase() {
entry?.let { iconManager.createIcons(it) }
// Updating the icons after creation shouldn't break anything
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -187,9 +213,11 @@ class IconManagerTest : SysuiTestCase() {
entry?.channel?.isImportantConversation = true
entry?.setSensitive(true, true)
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
entry?.setSensitive(false, false)
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index f89b9cd7410f..66a306e3fc02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -53,8 +53,6 @@ import android.provider.Settings.Global.HEADS_UP_ON
import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.log.core.LogLevel
@@ -150,7 +148,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
@Before
fun setUp() {
- val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+ val userId = ActivityManager.getCurrentUser()
+ val user = UserInfo(userId, "Current user", /* flags = */ 0)
+
+ deviceProvisionedController.currentUser = userId
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
provider.start()
@@ -823,6 +824,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
@Test
+ fun testShouldFsi_userSetupIncomplete() {
+ ensureUserSetupIncompleteFsiState()
+ assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldNotFsi_noHunOrKeyguard() {
ensureNoHunOrKeyguardFsiState()
assertShouldNotFsi(buildFsiEntry())
@@ -888,7 +896,8 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
var statusBarState: Int? = null,
var keyguardIsShowing: Boolean = false,
var keyguardIsOccluded: Boolean = false,
- var deviceProvisioned: Boolean = true
+ var deviceProvisioned: Boolean = true,
+ var currentUserSetup: Boolean = true
)
protected fun setState(state: State): Unit =
@@ -925,6 +934,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
keyguardStateController.isShowing = keyguardIsShowing
deviceProvisionedController.deviceProvisioned = deviceProvisioned
+ deviceProvisionedController.isCurrentUserSetup = currentUserSetup
}
protected fun ensureState(block: State.() -> Unit) =
@@ -999,6 +1009,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
hunSettingEnabled = false
keyguardIsShowing = false
deviceProvisioned = false
+ currentUserSetup = true
+ run(block)
+ }
+
+ protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
+ isInteractive = true
+ isDreaming = false
+ statusBarState = SHADE
+ hunSettingEnabled = false
+ keyguardIsShowing = false
+ deviceProvisioned = true
+ currentUserSetup = false
run(block)
}
@@ -1009,6 +1031,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
hunSettingEnabled = false
keyguardIsShowing = false
deviceProvisioned = true
+ currentUserSetup = true
run(block)
}
@@ -1216,7 +1239,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
neb.setImportance(importance)
val channel =
- NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+ NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
channel.isImportantConversation = isImportantConversation
neb.setChannel(channel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 718f99841292..3b78b7e492f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@ import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -102,6 +103,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.test.TestScope;
+
/**
* A helper class to create {@link ExpandableNotificationRow} (for both individual and group
* notifications).
@@ -140,6 +144,10 @@ public class NotificationTestHelper {
private final FakeFeatureFlags mFeatureFlags;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mRowInflaterTaskLogger;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final CoroutineContext mBgCoroutineContext =
+ mTestScope.getBackgroundScope().getCoroutineContext();
+ private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext();
public NotificationTestHelper(
Context context,
@@ -169,7 +177,10 @@ public class NotificationTestHelper {
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
- new IconBuilder(mContext));
+ new IconBuilder(mContext),
+ mTestScope,
+ mBgCoroutineContext,
+ mMainCoroutineContext);
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index ea4ae17de5e3..dcbb93aa496b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -49,6 +49,7 @@ import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -78,6 +79,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private CommandQueue mCommandQueue;
@Mock private QuickSettingsController mQuickSettingsController;
@Mock private ShadeViewController mShadeViewController;
+ @Mock private PanelExpansionInteractorImpl mPanelExpansionInteractor;
@Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -112,6 +114,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
mShadeController,
mCommandQueue,
mShadeViewController,
+ mPanelExpansionInteractor,
mRemoteInputQuickSettingsDisabler,
mMetricsLogger,
mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 443dd6a5dbc4..1463680de7d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -67,6 +68,7 @@ import javax.inject.Provider
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
@Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -195,7 +197,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Test
fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
- `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
+ `when`(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
view.onTouchEvent(event)
@@ -291,21 +293,22 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
return PhoneStatusBarViewController.Factory(
- Optional.of(sysuiUnfoldComponent),
- Optional.of(progressProvider),
- featureFlags,
- FakeSceneContainerFlags(),
- userChipViewModel,
- centralSurfacesImpl,
- statusBarWindowStateController,
- shadeControllerImpl,
- shadeViewController,
- windowRootView,
- shadeLogger,
- viewUtil,
- configurationController,
- mStatusOverlayHoverListenerFactory
- )
+ Optional.of(sysuiUnfoldComponent),
+ Optional.of(progressProvider),
+ featureFlags,
+ FakeSceneContainerFlags(),
+ userChipViewModel,
+ centralSurfacesImpl,
+ statusBarWindowStateController,
+ shadeControllerImpl,
+ shadeViewController,
+ panelExpansionInteractor,
+ windowRootView,
+ shadeLogger,
+ viewUtil,
+ configurationController,
+ mStatusOverlayHoverListenerFactory
+ )
.create(view)
.also { it.init() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index cdbbc9368db3..f9476400eb82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -747,14 +747,16 @@ public class ScrimControllerTest extends SysuiTestCase {
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -773,8 +775,9 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
+ mScrimController.setRawPanelExpansionFraction(1);
+ mScrimController.setTransitionToFullShadeProgress(1, 0);
finishAnimationsImmediately();
// Shade scrims are visible.
@@ -782,8 +785,10 @@ public class ScrimControllerTest extends SysuiTestCase {
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.setTransitionToFullShadeProgress(0, 0);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -813,14 +818,16 @@ public class ScrimControllerTest extends SysuiTestCase {
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -839,7 +846,6 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
mScrimController.setRawPanelExpansionFraction(1f);
finishAnimationsImmediately();
@@ -849,8 +855,11 @@ public class ScrimControllerTest extends SysuiTestCase {
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.setQsPosition(0f, 0);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
// All scrims are transparent.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 562aa6a4f497..b0b9bec4f721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -82,6 +82,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInte
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -103,6 +106,8 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import com.google.common.truth.Truth;
+import kotlin.Unit;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -1045,4 +1050,35 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
verify(mCentralSurfaces, never()).hideKeyguard();
verify(mPrimaryBouncerInteractor, never()).show(true);
}
+
+ @Test
+ public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() {
+ clearInvocations(mAlternateBouncerInteractor);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+ mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+ verify(mAlternateBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void altBouncerVisible_keyguardAuthenticatedBiometricsHandled() {
+ clearInvocations(mAlternateBouncerInteractor);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+ verify(mAlternateBouncerInteractor).hide();
+ }
+
+ @Test
+ public void fromAlternateBouncerTransitionStep() {
+ clearInvocations(mAlternateBouncerInteractor);
+ mStatusBarKeyguardViewManager.consumeFromAlternateBouncerTransitionSteps(
+ new TransitionStep(
+ /* from */ KeyguardState.ALTERNATE_BOUNCER,
+ /* to */ KeyguardState.GONE,
+ /* value */ 1f,
+ TransitionState.FINISHED,
+ "StatusBarKeyguardViewManagerTest"
+ )
+ );
+ verify(mAlternateBouncerInteractor).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 938b2f88d4b4..127a3d7b208b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -75,9 +75,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeControllerImpl;
-import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -257,7 +257,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
mOnUserInteractionCallback,
mock(NotificationPresenter.class),
- mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationShadeWindowController.class),
mActivityTransitionAnimator,
new ShadeAnimationInteractorLegacyImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index b0404a055a68..a8c5fc357c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -50,6 +50,7 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -296,6 +297,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
+ mock(PanelExpansionInteractor.class),
mock(QuickSettingsController.class),
mock(HeadsUpManager.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 3bf54a3b5498..57a89b29b77c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -100,10 +100,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
handler = handler
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
-
- // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
- // screen off.
- `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 974e396fe280..5206db4aa13a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -95,6 +95,7 @@ import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -292,6 +293,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
}
+ @Ignore("Causing breakages so ignoring to resolve, b/329099861")
@Test
@EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07b356b..0660d0095c7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -18,6 +18,7 @@ package com.android.systemui
import com.android.systemui.classifier.FakeClassifierModule
import com.android.systemui.data.FakeSystemUiDataLayerModule
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.FakeSystemPropertiesHelperModule
import com.android.systemui.log.FakeUiEventLoggerModule
import com.android.systemui.settings.FakeSettingsModule
import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
@@ -33,6 +34,7 @@ import dagger.Module
FakeConfigurationControllerModule::class,
FakeExecutorModule::class,
FakeFeatureFlagsClassicModule::class,
+ FakeSystemPropertiesHelperModule::class,
FakeSettingsModule::class,
FakeSplitShadeStateControllerModule::class,
FakeSystemClockModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 342855357fd2..80016046e9cf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -30,6 +30,7 @@ import java.io.IOException;
*
* To use:
* - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Use `atest -d` to prevent files being cleaned up
* - Watch the logcat with tag MEMORY to see the path to the .ahprof file
* - adb pull /path/to/something.ahprof
* - Download ahat from https://sites.google.com/corp/google.com/ahat/home
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 69b769eb2321..bc0bf9dd069f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -27,6 +27,9 @@ import com.android.systemui.coroutines.collectValues
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -56,6 +59,7 @@ import kotlinx.coroutines.test.runTest
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
SceneContainerFrameworkModule::class,
+ FaceWakeUpTriggersConfigModule::class,
]
)
interface SysUITestModule {
@@ -69,6 +73,11 @@ interface SysUITestModule {
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
@Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds
+ fun provideFaceAuthInteractor(
+ sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
+
companion object {
@Provides
fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 62a1aa93f35a..3d84291292c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -17,6 +17,7 @@ package com.android.systemui
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.app.trust.TrustManager
import android.os.UserManager
import android.service.notification.NotificationListenerService
import android.util.DisplayMetrics
@@ -27,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
@@ -36,6 +38,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
@@ -65,10 +68,12 @@ import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.GlobalSettings
import com.android.wm.shell.bubbles.Bubbles
import dagger.Binds
import dagger.Module
@@ -123,6 +128,10 @@ data class TestMocksModule(
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
+ @get:Provides val trustManager: TrustManager = mock(),
+ @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
+ @get:Provides val keyguardStateController: KeyguardStateController = mock(),
+ @get:Provides val globalSettings: GlobalSettings = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
@@ -131,6 +140,8 @@ data class TestMocksModule(
val sceneLogBuffer: LogBuffer = mock(),
@get:[Provides BiometricLog]
val biometricLogger: LogBuffer = mock(),
+ @get:[Provides FaceAuthLog]
+ val faceAuthLogger: LogBuffer = mock(),
@get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(),
// framework mocks
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef55573dc8..8a951365ecea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,10 +19,15 @@ package com.android.systemui.biometrics.data.repository
import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeFacePropertyRepository : FacePropertyRepository {
+@SysUISingleton
+class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository {
private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
override val sensorInfo: StateFlow<FaceSensorInfo?>
get() = faceSensorInfo
@@ -56,3 +61,8 @@ class FakeFacePropertyRepository : FacePropertyRepository {
currentCameraInfo.value = value
}
}
+
+@Module
+interface FakeFacePropertyRepositoryModule {
+ @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index bd30fb4cac2b..60d61ac4dcef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -68,6 +68,16 @@ class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPrope
)
}
+ /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */
+ fun supportsSideFps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.POWER_BUTTON,
+ sensorLocations = emptyMap(),
+ )
+ }
+
/** setProperties as if the device supports the rear fingerprint sensor. */
fun supportsRearFps() {
setProperties(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index c3af437dafdf..2e2cf9a61a8c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -78,6 +78,7 @@ class FakePromptRepository : PromptRepository {
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ com.android.systemui.Flags.constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
Utils.isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 1c8190eee773..fbb8ea22d615 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.data
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
@@ -35,6 +36,7 @@ import dagger.Module
FakeDisplayRepositoryModule::class,
FakeDisplayStateRepositoryModule::class,
FakeFingerprintPropertyRepositoryModule::class,
+ FakeFacePropertyRepositoryModule::class,
FakeTrustRepositoryModule::class,
]
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
index 66c6f86c452d..ebed922c423e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +26,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.deviceEntryFingerprintAuthInteractor by
Kosmos.Fixture {
DeviceEntryFingerprintAuthInteractor(
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
repository = deviceEntryFingerprintAuthRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 0d1a31f9605e..e73e2950bbb9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -18,8 +18,8 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,9 +34,12 @@ val Kosmos.deviceEntryInteractor by
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
- trustRepository = trustRepository,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ trustInteractor = trustInteractor,
flags = sceneContainerFlags,
deviceUnlockedInteractor = deviceUnlockedInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
new file mode 100644
index 000000000000..2f30d3431a1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+@SysUISingleton
+class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() {
+ private val fakeProperties = mutableMapOf<String, Any>()
+
+ override fun get(name: String): String {
+ return fakeProperties[name] as String
+ }
+
+ override fun get(name: String, def: String?): String {
+ return checkNotNull(fakeProperties[name] as String? ?: def)
+ }
+
+ override fun getBoolean(name: String, default: Boolean): Boolean {
+ return fakeProperties[name] as Boolean? ?: default
+ }
+
+ override fun setBoolean(name: String, value: Boolean) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: String) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: Int) {
+ fakeProperties[name] = value
+ }
+
+ override fun erase(name: String) {
+ fakeProperties.remove(name)
+ }
+}
+
+@Module
+interface FakeSystemPropertiesHelperModule {
+ @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 365d97f3ac15..d6f2f77ca67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -62,6 +62,7 @@ val Kosmos.featureFlagsClassicRelease by
}
val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() }
var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8eba73d..185deda950c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,11 +20,13 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
+ mainDispatcher = testDispatcher,
repository = keyguardTransitionRepository,
keyguardRepository = keyguardRepository,
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
new file mode 100644
index 000000000000..0ebf1642478e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 0b1385865d63..b34681ac0bdc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -2,6 +2,7 @@ package com.android.systemui.kosmos
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -9,3 +10,7 @@ var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
+ testScope.backgroundScope.coroutineContext
+}
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index 2a4dd3a43b88..09c8f87c99cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -23,6 +23,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
val Kosmos.panelExpansionInteractorImpl by Fixture {
PanelExpansionInteractorImpl(
- sceneInteractor = sceneInteractor,
+ sceneInteractor,
+ shadeInteractor,
+ shadeAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index d2dd200faa07..6d24e2aec089 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeAnimationRepository
@@ -24,5 +25,9 @@ var Kosmos.shadeAnimationInteractor: ShadeAnimationInteractor by
Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
Kosmos.Fixture {
- ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+ ShadeAnimationInteractorSceneContainerImpl(
+ testScope.backgroundScope,
+ shadeAnimationRepository,
+ sceneInteractor
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index d3a8e0c5970c..0950f04aeea8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -18,7 +18,19 @@ package com.android.systemui.statusbar.notification.icon
import android.content.pm.launcherApps
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
val Kosmos.iconManager by
- Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
+ Kosmos.Fixture {
+ IconManager(
+ commonNotifCollection,
+ launcherApps,
+ iconBuilder,
+ applicationCoroutineScope,
+ backgroundCoroutineContext,
+ mainCoroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 7a86c4f73a3f..c6684af9d5eb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -30,9 +30,9 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
import com.android.systemui.shade.shadeController
-import com.android.systemui.shade.shadeViewController
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
@@ -78,7 +78,7 @@ val Kosmos.statusBarNotificationActivityStarter by
statusBarNotificationActivityStarterLogger,
onUserInteractionCallback,
notificationPresenter,
- shadeViewController,
+ panelExpansionInteractor,
notificationShadeWindowController,
activityTransitionAnimator,
shadeAnimationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
index aa2c2a2e1ec3..ebcabf82c166 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
@@ -39,8 +39,16 @@ class FakeDeviceProvisionedController : DeviceProvisionedController {
callbacks.toSet().forEach { it.onUserSwitched() }
}
- fun setUserSetup(userId: Int) {
- usersSetup.add(userId)
+ fun setUserSetup(userId: Int, isSetup: Boolean = true) {
+ if (isSetup) {
+ usersSetup.add(userId)
+ } else {
+ usersSetup.remove(userId)
+ }
callbacks.toSet().forEach { it.onUserSetupChanged() }
}
+
+ fun setCurrentUserSetup(isSetup: Boolean) {
+ setUserSetup(currentUser, isSetup)
+ }
}
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958f1d53..000000000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 10e6ed4542c7..004f37c16757 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,8 @@ import android.hardware.camera2.extension.Request;
import android.hardware.camera2.extension.SizeList;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.params.ColorSpaceProfiles;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageReader;
@@ -526,7 +528,7 @@ public class CameraExtensionsProxyService extends Service {
*/
public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
// Basic extensions are deprecated starting with extension version 1.5
return new Pair<>(new PreviewExtenderImpl() {
@@ -711,7 +713,7 @@ public class CameraExtensionsProxyService extends Service {
* @hide
*/
public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
if (EFV_SUPPORTED) {
return new EyesFreeVideographyAdvancedExtenderImpl();
@@ -1228,7 +1230,6 @@ public class CameraExtensionsProxyService extends Service {
return null;
}
-
}
private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1585,11 +1586,13 @@ public class CameraExtensionsProxyService extends Service {
Camera2SessionConfigImpl sessionConfig;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface,
+ imageCaptureSurface, postviewSurface);
OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl,
// Image Analysis Output is currently only supported in CameraX
mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
- mOutputPostviewSurfaceImpl);
+ mOutputPostviewSurfaceImpl, outputsColorSpace);
sessionConfig = mSessionProcessor.initSession(cameraId,
getCharacteristicsMap(charsMapNative),
@@ -1616,6 +1619,11 @@ public class CameraExtensionsProxyService extends Service {
}
ret.outputConfigs.add(entry);
}
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.colorSpace = sessionConfig.getColorSpace();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
ret.sessionType = -1;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
@@ -1720,6 +1728,24 @@ public class CameraExtensionsProxyService extends Service {
public void binderDied() {
mSessionProcessor.deInitSession();
}
+
+ // Get the color space of the output configurations. All of the OutputSurfaces
+ // can be assumed to have the same color space so return the color space
+ // of any non-null OutputSurface
+ private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface,
+ OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
+ int colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+
+ if (previewSurface.surface != null) {
+ colorSpace = previewSurface.colorSpace;
+ } else if (imageCaptureSurface.surface != null) {
+ colorSpace = imageCaptureSurface.colorSpace;
+ } else if (postviewSurface.surface != null) {
+ colorSpace = postviewSurface.colorSpace;
+ }
+
+ return colorSpace;
+ }
}
private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1727,6 +1753,17 @@ public class CameraExtensionsProxyService extends Service {
private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+ private int mColorSpace;
+
+ public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+ OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+ OutputSurfaceImpl postviewOutput, int colorSpace) {
+ mOutputPreviewSurfaceImpl = previewOutput;
+ mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+ mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+ mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = colorSpace;
+ }
public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
@@ -1735,6 +1772,7 @@ public class CameraExtensionsProxyService extends Service {
mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
@Override
@@ -1756,6 +1794,11 @@ public class CameraExtensionsProxyService extends Service {
public OutputSurfaceImpl getPostviewOutputSurface() {
return mOutputPostviewSurfaceImpl;
}
+
+ @Override
+ public int getColorSpace() {
+ return mColorSpace;
+ }
}
private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1764,11 +1807,10 @@ public class CameraExtensionsProxyService extends Service {
private final int mImageFormat;
private final int mDataspace;
private final long mUsage;
+ private final long mDynamicRangeProfile;
public OutputSurfaceImplStub(OutputSurface outputSurface) {
mSurface = outputSurface.surface;
- mSize = new Size(outputSurface.size.width, outputSurface.size.height);
- mImageFormat = outputSurface.imageFormat;
if (mSurface != null) {
mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
@@ -1776,6 +1818,9 @@ public class CameraExtensionsProxyService extends Service {
mDataspace = -1;
mUsage = 0;
}
+ mDynamicRangeProfile = outputSurface.dynamicRangeProfile;
+ mSize = new Size(outputSurface.size.width, outputSurface.size.height);
+ mImageFormat = outputSurface.imageFormat;
}
@Override
@@ -1802,6 +1847,12 @@ public class CameraExtensionsProxyService extends Service {
public long getUsage() {
return mUsage;
}
+
+ @Override
+ public long getDynamicRangeProfile() {
+ return mDynamicRangeProfile;
+ }
+
}
private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2531,6 +2582,11 @@ public class CameraExtensionsProxyService extends Service {
private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
CameraOutputConfig ret = new CameraOutputConfig();
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.dynamicRangeProfile = output.getDynamicRangeProfile();
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
ret.outputId = new OutputConfigId();
ret.outputId.id = output.getId();
ret.physicalCameraId = output.getPhysicalCameraId();
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7a50a6..8cafb433736f 100644
--- a/ravenwood/README.md
+++ b/ravenwood/README.md
@@ -1,9 +1,11 @@
# Ravenwood
-Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host.
+Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host.
Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Existing Ravenwood tests will continue running, but support has moved to a self-service model.
+
## Background
Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness.
diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md
index 4b2f96804c97..c059cabd14e2 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -4,7 +4,7 @@ By default, Android APIs aren’t opted-in to Ravenwood, and they default to thr
To opt-in to supporting an API under Ravenwood, you can use the inline annotations documented below to customize your API behavior when running under Ravenwood. Because these annotations are inline in the relevant platform source code, they serve as valuable reminders to future API maintainers of Ravenwood support expectations.
-> **Note:** to ensure that API teams are well-supported during early Ravenwood onboarding, the Ravenwood team is manually maintaining an allow-list of classes that are able to use Ravenwood annotations. Please reach out to ravenwood@ so we can offer design advice and allow-list your APIs.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Currently supported APIs will continue working, but the addition of new APIs is not currently being supported. There is an allowlist that restricts where Ravenwood-specific annotations can be used, and that allowlist is not being expanded while development is paused.
These Ravenwood-specific annotations have no bearing on the status of an API being public, `@SystemApi`, `@TestApi`, `@hide`, etc. Ravenwood annotations are an orthogonal concept that are only consumed by the internal `hoststubgen` tool during a post-processing step that generates the Ravenwood runtime environment. Teams that own APIs can continue to refactor opted-in `@hide` implementation details, as long as the test-visible behavior continues passing.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303ad9e57..2d531e73c3fd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1706,20 +1706,101 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
- @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
+ @RequiresPermission(allOf = {
+ Manifest.permission.STATUS_BAR_SERVICE,
+ Manifest.permission.MANAGE_ACCESSIBILITY
+ })
public void notifyQuickSettingsTilesChanged(
- @UserIdInt int userId, List<ComponentName> tileComponentNames) {
- mSecurityPolicy.enforceCallingPermission(
+ @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+
+ mContext.enforceCallingPermission(
Manifest.permission.STATUS_BAR_SERVICE,
/* function= */ "notifyQuickSettingsTilesChanged");
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY,
+ /* function= */ "notifyQuickSettingsTilesChanged");
+
+ if (DEBUG) {
+ Slog.d(LOG_TAG, TextUtils.formatSimple(
+ "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
+ userId, tileComponentNames));
+ }
+ final Set<ComponentName> newTileComponentNames = new ArraySet<>(tileComponentNames);
+ final Set<ComponentName> addedTiles;
+ final Set<ComponentName> removedTiles;
+ final Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfo;
+ final Map<ComponentName, ComponentName> a11yFeatureToTileService;
- Slog.d(LOG_TAG, TextUtils.formatSimple(
- "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
- userId, tileComponentNames));
- // TODO (b/314843909): in the follow up cl
// update in-memory copy of QS_TILES in AccessibilityManager
- // update Settings.Secure.ACCESSIBILITY_QS_TARGETS and its in-memory copy
- // show full device control warning if needed (b/314850435)
+ synchronized (mLock) {
+ AccessibilityUserState userState = getUserStateLocked(userId);
+
+ tileServiceToA11yServiceInfo = userState.getTileServiceToA11yServiceInfoMapLocked();
+ a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+
+ ArraySet<ComponentName> currentTiles = userState.getA11yQsTilesInQsPanel();
+ // Find newly added tiles
+ addedTiles = newTileComponentNames
+ .stream()
+ .filter(tileComponentName -> !currentTiles.contains(tileComponentName))
+ .collect(Collectors.toSet());
+ // Find newly removed tiles
+ removedTiles = currentTiles
+ .stream()
+ .filter(tileComponentName -> !newTileComponentNames.contains(tileComponentName))
+ .collect(Collectors.toSet());
+
+ if (addedTiles.isEmpty() && removedTiles.isEmpty()) {
+ return;
+ }
+
+ userState.updateA11yTilesInQsPanelLocked(newTileComponentNames);
+ }
+
+ List<String> a11yFeaturesToEnable = new ArrayList<>();
+ List<String> a11yFeaturesToRemove = new ArrayList<>();
+ // Find the framework features to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> frameworkFeatureWithTile :
+ ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+ String a11yFeature = frameworkFeatureWithTile.getKey().flattenToString();
+ ComponentName tile = frameworkFeatureWithTile.getValue();
+ if (addedTiles.contains(tile)) {
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tile)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Find the accessibility service/activity to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> a11yFeatureWithTileService :
+ a11yFeatureToTileService.entrySet()) {
+ String a11yFeature = a11yFeatureWithTileService.getKey().flattenToString();
+ ComponentName tileService = a11yFeatureWithTileService.getValue();
+ if (addedTiles.contains(tileService)) {
+ AccessibilityServiceInfo serviceInfo = tileServiceToA11yServiceInfo.getOrDefault(
+ tileService, null);
+ if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
+ // TODO(b/314850435): show full device control warning if needed after
+ // SysUI QS Panel can update live
+ continue;
+ }
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tileService)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
+ if (!a11yFeaturesToEnable.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToEnable, userId);
+ }
+
+ if (!a11yFeaturesToRemove.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToRemove, userId);
+ }
}
/**
@@ -3661,18 +3742,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
/**
* Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
* and a side loaded service can't spoof the package name of the default service.
+ * <p>
+ * 1. Remove the target if the target is no longer installed on the device <br/>
+ * 2. Add the target if the target is enabled and the target's tile is in the QS Panel <br/>
+ * </p>
*/
private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
- final Set<String> targets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
- final int lastSize = targets.size();
- if (lastSize == 0) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
+ final Set<String> targets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+
// Removes the targets that are no longer installed on the device.
boolean somethingChanged = targets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
+ // Add the target if the a11y service is enabled and the tile exist in QS panel
+ Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
+ Map<ComponentName, ComponentName> a11yFeatureToTileService =
+ userState.getA11yFeatureToTileService();
+ Set<ComponentName> currentA11yTilesInQsPanel = userState.getA11yQsTilesInQsPanel();
+ for (ComponentName enabledService : enabledServices) {
+ ComponentName tileService =
+ a11yFeatureToTileService.getOrDefault(enabledService, null);
+ if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
+ somethingChanged |= targets.add(enabledService.flattenToString());
+ }
+ }
+
if (!somethingChanged) {
return;
}
@@ -3700,14 +3798,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
- final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+ final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_BUTTON,
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
- new Pair<>(UserShortcutType.QUICK_SETTINGS,
- Settings.Secure.ACCESSIBILITY_QS_TARGETS)
- );
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+ if (android.view.accessibility.Flags.a11yQsShortcut()) {
+ shortcutTypeAndShortcutSetting.add(
+ new Pair<>(UserShortcutType.QUICK_SETTINGS,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+ }
final ComponentName serviceName = service.getComponentName();
for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 063eafebdfcb..4b128f75f4d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -66,6 +66,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* Class that hold states and settings per user and share between
@@ -104,6 +106,15 @@ class AccessibilityUserState {
final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
+ /**
+ * The QuickSettings tiles in the QS Panel. This can be different from
+ * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+ * TileService's or the a11y framework tile component names (e.g.
+ * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
+ * A11y Feature's component names.
+ */
+ private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
+
private final ServiceInfoChangeListener mServiceInfoChangeListener;
private ComponentName mServiceChangingSoftKeyboardMode;
@@ -566,7 +577,9 @@ class AccessibilityUserState {
pw.println("}");
pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton);
pw.println("}");
- pw.append(" qs shortcut targets:" + mAccessibilityQsTargets);
+ pw.append(" qs shortcut targets:").append(mAccessibilityQsTargets.toString());
+ pw.println();
+ pw.append(" a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
pw.println();
pw.append(" Bound services:{");
final int serviceCount = mBoundServices.size();
@@ -1100,10 +1113,46 @@ class AccessibilityUserState {
return new ArraySet<>(mAccessibilityQsTargets);
}
+ public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
+ mA11yTilesInQsPanel.clear();
+ mA11yTilesInQsPanel.addAll(componentNames);
+ }
+
+ /**
+ * Returns a copy of the a11y tiles that are in the QuickSettings panel
+ */
+ public ArraySet<ComponentName> getA11yQsTilesInQsPanel() {
+ return new ArraySet<>(mA11yTilesInQsPanel);
+ }
+
+ /**
+ * Returns a map of AccessibilityService or AccessibilityShortcut to its provided TileService
+ */
public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
featureToTileServiceMap.putAll(mA11yServiceToTileService);
featureToTileServiceMap.putAll(mA11yActivityToTileService);
return featureToTileServiceMap;
}
+
+ /**
+ * Returns a map of TileService's componentName to the AccessibilityServiceInfo it ties to.
+ */
+ public Map<ComponentName, AccessibilityServiceInfo> getTileServiceToA11yServiceInfoMapLocked() {
+ Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfoMap =
+ new ArrayMap<>();
+ Map<ComponentName, AccessibilityServiceInfo> a11yServiceToServiceInfoMap =
+ mInstalledServices.stream().collect(
+ Collectors.toMap(
+ AccessibilityServiceInfo::getComponentName,
+ Function.identity()));
+ for (Map.Entry<ComponentName, ComponentName> serviceToTile :
+ mA11yServiceToTileService.entrySet()) {
+ if (a11yServiceToServiceInfoMap.containsKey(serviceToTile.getKey())) {
+ tileServiceToA11yServiceInfoMap.put(serviceToTile.getValue(),
+ a11yServiceToServiceInfoMap.get(serviceToTile.getKey()));
+ }
+ }
+ return tileServiceToA11yServiceInfoMap;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef7dcdc..e13994e75690 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@ public class TouchState {
mReceivedPointersDown |= pointerFlag;
mReceivedPointers[pointerId].set(
event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
- mPrimaryPointerId = pointerId;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mPrimaryPointerId = pointerId;
+ }
}
/**
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1749ee333b8e..16fe0077d6ff 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4025,8 +4025,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
}
- throw new IllegalArgumentException(
- providerComponent + " is not a valid AppWidget provider");
+ // Either the provider does not exist or the caller does not have permission to access its
+ // previews.
+ return null;
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ca2a3ddc49bc..a55b8d008a68 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -171,6 +171,7 @@ import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillFeatureFlags;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillCommitReason;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -1498,7 +1499,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
mIsPrimaryCredential = isPrimaryCredential;
- mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+ mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f4bf2e..29ae2027fc3a 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
jstemmer@google.com
martinoh@google.com
-millmore@google.com
niamhfw@google.com
piee@google.com
philippov@google.com
rthakohov@google.com
-sarpm@google.com \ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com \ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab0980a22b..340bc327fc7f 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
- ]
+ ],
+ "keywords": ["primary-device"]
},
{
"name": "CtsHardwareTestCases",
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7ab490..ecd14ce67d7e 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -79,7 +80,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
Trace.beginSection(
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info);
+ onProjectionStart(info.getPackageName());
} finally {
Trace.endSection();
}
@@ -124,14 +125,6 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
- // These packages are exempted from screen share protection.
- private ArraySet<String> getExemptedPackages() {
- final ArraySet<String> exemptedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
- // TODO(b/323361046) - Add sys ui recorder package.
- return exemptedPackages;
- }
-
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
ArraySet<String> exemptedPackages) {
@@ -179,9 +172,22 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
- private void onProjectionStart(MediaProjectionInfo info) {
- if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) {
- Log.w(TAG, info.getPackageName() + " is exempted from screen share protection.");
+ private boolean canRecordSensitiveContent(@NonNull String packageName) {
+ return getContext().getPackageManager()
+ .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ packageName) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ // These packages are exempted from screen share protection.
+ private ArraySet<String> getExemptedPackages() {
+ return SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ }
+
+ private void onProjectionStart(String packageName) {
+ // exempt on device screen recorder as well.
+ if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
+ || canRecordSensitiveContent(packageName)) {
+ Log.w(TAG, packageName + " is exempted from screen share protection.");
return;
}
// TODO(b/324447419): move GlobalSettings lookup to background thread
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 130a7333959d..1334a95e244d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -113,6 +113,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
@@ -284,6 +285,11 @@ public class AccountManagerService
private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+ private static Histogram sResponseLatency = new Histogram(
+ "app.value_high_authenticator_response_latency",
+ new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f)
+ );
+
/**
* This should only be called by system code. One should only call this after the service
* has started.
@@ -4937,6 +4943,9 @@ public class AccountManagerService
protected boolean mCanStartAccountManagerActivity = false;
protected final UserAccounts mAccounts;
+ private int mAuthenticatorUid;
+ private long mBindingStartTime;
+
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
boolean authDetailsRequired) {
@@ -4974,6 +4983,10 @@ public class AccountManagerService
}
IAccountManagerResponse getResponseAndClose() {
+ if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+ sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+ SystemClock.uptimeMillis() - mBindingStartTime);
+ }
if (mResponse == null) {
close();
return null;
@@ -5353,7 +5366,8 @@ public class AccountManagerService
mContext.unbindService(this);
return false;
}
-
+ mAuthenticatorUid = authenticatorInfo.uid;
+ mBindingStartTime = SystemClock.uptimeMillis();
return true;
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 52988460606c..0012b3d86552 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -204,6 +204,7 @@ import android.os.TransactionTooLargeException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.service.voice.HotwordDetectionService;
import android.service.voice.VisualQueryDetectionService;
import android.service.wearable.WearableSensingService;
@@ -4518,13 +4519,14 @@ public final class ActiveServices {
}
// TODO(b/265746493): Special case for HotwordDetectionService,
- // VisualQueryDetectionService and WearableSensingService.
+ // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService
// Need a cleaner way to append this seInfo.
private String generateAdditionalSeInfoFromService(Intent service) {
if (service != null && service.getAction() != null
&& (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
|| service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
- || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
+ || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE)
+ || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) {
return ":isolatedComputeApp";
}
return "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5e6ff55f4e94..447dfd95e034 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9011,7 +9011,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// cleaning up the old proxies.
VMRuntime.getRuntime().requestConcurrentGC();
}
- }, BackgroundThread.getHandler());
+ }, mHandler);
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5521381e8908..0a6e9d3198cb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -286,9 +286,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// when the flag is fused on.
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
- // TODO: Use the trunk stable flag.
- private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
-
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -766,7 +763,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
// outgoing broadcasts.
// TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
- if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
+ if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
r.callerApp.processName, r.callerApp.uid);
if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3d695bcdb767..5cb8b954a2ba 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -674,14 +674,16 @@ final class ProcessServiceRecord {
return mScheduleServiceTimeoutPending;
}
- @GuardedBy("mService")
void onProcessUnfrozen() {
- scheduleServiceTimeoutIfNeededLocked();
+ synchronized (mService) {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
}
- @GuardedBy("mService")
void onProcessFrozenCancelled() {
- scheduleServiceTimeoutIfNeededLocked();
+ synchronized (mService) {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c24256f5b1..14428c41ef26 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -157,7 +157,7 @@ public class AudioDeviceInventory {
* corresponding peers in case of BLE
*/
void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
- @AudioDeviceCategory int category) {
+ @AudioDeviceCategory int category, boolean userDefined) {
if (!isBluetoothOutDevice(deviceType)) {
return;
}
@@ -167,7 +167,11 @@ public class AudioDeviceInventory {
ads = findBtDeviceStateForAddress(peerAddress, deviceType);
}
if (ads != null) {
- if (ads.getAudioDeviceCategory() != category) {
+ // if category is user defined allow to change back to unknown otherwise
+ // do not reset the category back to unknown since it might have been set
+ // before by the user
+ if (ads.getAudioDeviceCategory() != category && (userDefined
+ || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
@@ -220,9 +224,9 @@ public class AudioDeviceInventory {
void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
@AudioDeviceCategory int btAudioDeviceCategory) {
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
}
@AudioDeviceCategory
@@ -1733,7 +1737,7 @@ public class AudioDeviceInventory {
purgeDevicesRoles_l();
} else {
addAudioDeviceInInventoryIfNeeded(device, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2023,7 +2027,7 @@ public class AudioDeviceInventory {
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2357,7 +2361,7 @@ public class AudioDeviceInventory {
DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -2488,7 +2492,7 @@ public class AudioDeviceInventory {
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 48bf9f4967bc..e915688b95b1 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -791,14 +791,12 @@ public class AuthService extends SystemService {
private void registerAuthenticators() {
BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider();
- handlerProvider.getFingerprintHandler().post(() ->
- registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
- mInjector.getFingerprintConfiguration(getContext()), getContext(),
- mInjector.getFingerprintService()));
- handlerProvider.getFaceHandler().post(() ->
- registerFaceSensors(mInjector.getFaceAidlInstances(),
- mInjector.getFaceConfiguration(getContext()), getContext(),
- mInjector.getFaceService()));
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()), getContext(),
+ mInjector.getFingerprintService(), handlerProvider);
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()), getContext(),
+ mInjector.getFaceService(), handlerProvider);
registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
}
@@ -854,30 +852,38 @@ public class AuthService extends SystemService {
*/
private static void registerFaceSensors(final String[] faceAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFaceService faceService) {
- final FaceSensorConfigurations mFaceSensorConfigurations =
- new FaceSensorConfigurations(hidlConfigStrings != null
- && hidlConfigStrings.length > 0);
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
+ final IFaceService faceService, final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (faceAidlInstances == null || faceAidlInstances.length == 0)) {
+ Slog.d(TAG, "No face sensors.");
+ return;
}
- if (faceAidlInstances != null && faceAidlInstances.length > 0) {
- mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
- name -> IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFaceHandler().post(() -> {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
- if (faceService != null) {
- try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
}
- } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Face configuration exists, but FaceService is null.");
- }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ });
}
/**
@@ -885,30 +891,40 @@ public class AuthService extends SystemService {
*/
private static void registerFingerprintSensors(final String[] fingerprintAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFingerprintService fingerprintService) {
- final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
- new FingerprintSensorConfigurations(!(hidlConfigStrings != null
- && hidlConfigStrings.length > 0));
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
+ final IFingerprintService fingerprintService,
+ final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) {
+ Slog.d(TAG, "No fingerprint sensors.");
+ return;
}
- if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
- mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
- name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFingerprintHandler().post(() -> {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
}
- } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
- }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index a923daaa5a51..e57886127e63 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -27,9 +30,9 @@ public class BiometricHandlerProvider {
private static final BiometricHandlerProvider sBiometricHandlerProvider =
new BiometricHandlerProvider();
- private final Handler mBiometricsCallbackHandler;
- private final Handler mFingerprintHandler;
- private final Handler mFaceHandler;
+ private Handler mBiometricsCallbackHandler;
+ private Handler mFingerprintHandler;
+ private Handler mFaceHandler;
/**
* @return an instance of {@link BiometricHandlerProvider} which contains the three
@@ -39,16 +42,16 @@ public class BiometricHandlerProvider {
return sBiometricHandlerProvider;
}
- private BiometricHandlerProvider() {
- mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
- mFingerprintHandler = getNewHandler("FingerprintHandler");
- mFaceHandler = getNewHandler("FaceHandler");
- }
+ private BiometricHandlerProvider() {}
/**
* @return the handler to process all biometric callback operations
*/
public synchronized Handler getBiometricCallbackHandler() {
+ if (mBiometricsCallbackHandler == null) {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler",
+ THREAD_PRIORITY_DISPLAY);
+ }
return mBiometricsCallbackHandler;
}
@@ -56,6 +59,9 @@ public class BiometricHandlerProvider {
* @return the handler to process all face related biometric operations
*/
public synchronized Handler getFaceHandler() {
+ if (mFaceHandler == null) {
+ mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFaceHandler;
}
@@ -63,12 +69,15 @@ public class BiometricHandlerProvider {
* @return the handler to process all fingerprint related biometric operations
*/
public synchronized Handler getFingerprintHandler() {
+ if (mFingerprintHandler == null) {
+ mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFingerprintHandler;
}
- private Handler getNewHandler(String tag) {
+ private Handler getNewHandler(String tag, int priority) {
if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag);
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
handlerThread.start();
return new Handler(handlerThread.getLooper());
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
new file mode 100644
index 000000000000..133c79f81bb5
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -0,0 +1,129 @@
+/*
+ * 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.crashrecovery;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.net.ConnectivityModuleConnector;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.pm.ApexManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides helper methods for the CrashRecovery APEX
+ *
+ * @hide
+ */
+public final class CrashRecoveryHelper {
+ private static final String TAG = "CrashRecoveryHelper";
+
+ private final ApexManager mApexManager;
+ private final Context mContext;
+ private final ConnectivityModuleConnector mConnectivityModuleConnector;
+
+
+ /** @hide */
+ public CrashRecoveryHelper(@NonNull Context context) {
+ mContext = context;
+ mApexManager = ApexManager.getInstance();
+ mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance();
+ }
+
+ /**
+ * Returns true if the package name is the name of a module.
+ * If the package is an APK inside an APEX then it will use the parent's APEX package name
+ * do determine if it is a module or not.
+ * @hide
+ */
+ @AnyThread
+ public boolean isModule(@NonNull String packageName) {
+ String apexPackageName =
+ mApexManager.getActiveApexPackageNameContainingPackage(packageName);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ return pm.getModuleInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException ignore) {
+ return false;
+ }
+ }
+
+ /**
+ * Register health listeners for explicit package failures.
+ * Currently only registering for Connectivity Module health.
+ * @hide
+ */
+ public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+ // register listener for ConnectivityModule
+ mConnectivityModuleConnector.registerHealthListener(
+ packageName -> {
+ final VersionedPackage pkg = getVersionedPackage(packageName);
+ if (pkg == null) {
+ Slog.wtf(TAG, "NetworkStack failed but could not find its package");
+ return;
+ }
+ final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
+ PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+ });
+ }
+
+ @Nullable
+ private VersionedPackage getVersionedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm == null || TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ try {
+ final long versionCode = getPackageInfo(packageName).getLongVersionCode();
+ return new VersionedPackage(packageName, versionCode);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets PackageInfo for the given package. Matches any user and apex.
+ *
+ * @throws PackageManager.NameNotFoundException if no such package is installed.
+ */
+ private PackageInfo getPackageInfo(String packageName)
+ throws PackageManager.NameNotFoundException {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
+ // flag, so make two separate attempts to get the package info.
+ // We don't need both flags at the same time because we assume
+ // apex files are always installed for all users.
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
+ } catch (PackageManager.NameNotFoundException e) {
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 000000000000..daa02111f71f
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5ab852f..10030b3c9176 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -30,8 +31,7 @@ import java.util.function.BooleanSupplier;
class BrightnessRangeController {
private final HighBrightnessModeController mHbmController;
- private final NormalBrightnessModeController mNormalBrightnessModeController =
- new NormalBrightnessModeController();
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
private final HdrClamper mHdrClamper;
@@ -45,17 +45,21 @@ class BrightnessRangeController {
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
+ new NormalBrightnessModeController(),
new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
displayToken, info);
}
+ @VisibleForTesting
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
DisplayDeviceInfo info) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mUseHdrClamper = flags.isHdrClamperEnabled();
mUseNbmController = flags.isNbmControllerEnabled();
if (mUseNbmController) {
@@ -126,8 +130,11 @@ class BrightnessRangeController {
float getCurrentBrightnessMax() {
- if (mUseNbmController && mHbmController.getHighBrightnessMode()
- == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ // nbmController might adjust maxBrightness only if device does not support HBM or
+ // hbm is currently not allowed
+ if (mUseNbmController
+ && (!mHbmController.deviceSupportsHbm()
+ || !mHbmController.isHbmCurrentlyAllowed())) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47eb542..a7748f4fae98 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@ import java.io.PrintWriter;
* </p>
*/
abstract class DisplayDevice {
+ /**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+ * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+ * and rounded to the nearest cm).
+ */
+ static final float MAX_ANISOTROPY = 1.025f;
private static final String TAG = "DisplayDevice";
private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
@@ -69,13 +78,21 @@ abstract class DisplayDevice {
// Do not use for any other purpose.
DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
- public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
Context context) {
+ this(displayAdapter, displayToken, uniqueId, context, false);
+ }
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ Context context, boolean isAnisotropyCorrectionEnabled) {
mDisplayAdapter = displayAdapter;
mDisplayToken = displayToken;
mUniqueId = uniqueId;
mDisplayDeviceConfig = null;
mContext = context;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
/**
@@ -143,8 +160,17 @@ abstract class DisplayDevice {
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
final boolean isRotated = mCurrentOrientation == ROTATION_90
|| mCurrentOrientation == ROTATION_270;
- return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
- : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ var width = displayDeviceInfo.width;
+ var height = displayDeviceInfo.height;
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+ && displayDeviceInfo.xDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+ height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+ } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+ width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5);
+ }
+ }
+ return isRotated ? new Point(height, width) : new Point(width, height);
}
/**
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index ab7c503bcb83..a12d2481330b 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -42,6 +42,9 @@ import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.display.utils.DebugUtils;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Listens for Skin thermal sensor events, disables external displays if thermal status becomes
* equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
@@ -106,6 +109,10 @@ class ExternalDisplayPolicy {
private final ExternalDisplayStatsService mExternalDisplayStatsService;
@ThrottlingStatus
private volatile int mStatus = THROTTLING_NONE;
+ //@GuardedBy("mSyncRoot")
+ private boolean mIsBootCompleted;
+ //@GuardedBy("mSyncRoot")
+ private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
ExternalDisplayPolicy(@NonNull final Injector injector) {
mInjector = injector;
@@ -121,6 +128,17 @@ class ExternalDisplayPolicy {
* Starts listening for temperature changes.
*/
void onBootCompleted() {
+ synchronized (mSyncRoot) {
+ mIsBootCompleted = true;
+ for (var displayId : mDisplayIdsWaitingForBootCompletion) {
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay != null) {
+ handleExternalDisplayConnectedLocked(logicalDisplay);
+ }
+ }
+ mDisplayIdsWaitingForBootCompletion.clear();
+ }
+
if (!mFlags.isConnectedDisplayManagementEnabled()) {
if (DEBUG) {
Slog.d(TAG, "External display management is not enabled on your device:"
@@ -189,6 +207,11 @@ class ExternalDisplayPolicy {
return;
}
+ if (!mIsBootCompleted) {
+ mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
if ((Build.IS_ENG || Build.IS_USERDEBUG)
@@ -227,7 +250,12 @@ class ExternalDisplayPolicy {
return;
}
- mExternalDisplayStatsService.onDisplayDisconnected(logicalDisplay.getDisplayIdLocked());
+ var displayId = logicalDisplay.getDisplayIdLocked();
+ if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
+ return;
+ }
+
+ mExternalDisplayStatsService.onDisplayDisconnected(displayId);
}
/**
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd5bb2a..47176fe331bf 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@ class HighBrightnessModeController {
}
float getCurrentBrightnessMax() {
- if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+ if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
// Either the device doesn't support HBM, or HBM range is currently allowed (device
// it in a high-lux environment). In either case, return the highest brightness
// level supported by the device.
@@ -356,7 +356,7 @@ class HighBrightnessModeController {
return event.getStartTimeMillis();
}
- private boolean isCurrentlyAllowed() {
+ boolean isHbmCurrentlyAllowed() {
// Returns true if HBM is allowed (above the ambient lux threshold) and there's still
// time within the current window for additional HBM usage. We return false if there is an
// HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@ class HighBrightnessModeController {
&& !mIsBlockedByLowPowerMode);
}
- private boolean deviceSupportsHbm() {
+ boolean deviceSupportsHbm() {
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
@@ -462,7 +462,7 @@ class HighBrightnessModeController {
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
+ ", remainingAllowedTime: " + remainingTime
+ ", isLuxHigh: " + mIsInAllowedAmbientRange
- + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+ + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
+ ", isHdrLayerPresent: " + mIsHdrLayerPresent
+ ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
+ ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@ class HighBrightnessModeController {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
} else if (mIsHdrLayerPresent) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
- } else if (isCurrentlyAllowed()) {
+ } else if (isHbmCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0a7eff..b2fd9edf61fe 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
- getContext());
+ getContext(),
+ getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
mPhysicalDisplayId = physicalDisplayId;
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d619bd3..5eaaf3504e85 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@ import android.view.SurfaceControl;
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.wm.utils.DisplayInfoOverrides;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -204,7 +203,28 @@ final class LogicalDisplay {
private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
new SparseArray<>();
+ /**
+ * If the aspect ratio of the resolution of the display does not match the physical aspect
+ * ratio of the display, then without this feature enabled, picture would appear stretched to
+ * the user. This is because applications assume that they are rendered on square pixels
+ * (meaning density of pixels in x and y directions are equal). This would result into circles
+ * appearing as ellipses to the user.
+ * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+ * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+ * were square and occupied the full display.
+ * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+ * physical pixels in the current display resolution.
+ * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+ * display will stretch the pixels to fill the display fully.
+ */
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+ this(displayId, layerStack, primaryDisplayDevice, false);
+ }
+
+ LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+ boolean isAnisotropyCorrectionEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@ final class LogicalDisplay {
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@ final class LogicalDisplay {
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
+ if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+ if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+ } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+ maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+ }
+ }
+
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@ final class LogicalDisplay {
physWidth -= maskingInsets.left + maskingInsets.right;
physHeight -= maskingInsets.top + maskingInsets.bottom;
+ var displayLogicalWidth = displayInfo.logicalWidth;
+ var displayLogicalHeight = displayInfo.logicalHeight;
+
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+ && displayDeviceInfo.yDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+ if (rotated) {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ } else {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ }
+ } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+ < displayDeviceInfo.yDpi) {
+ var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+ if (rotated) {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ } else {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ }
+ }
+ }
+
// Determine whether the width or height is more constrained to be scaled.
// physWidth / displayInfo.logicalWidth => letter box
// or physHeight / displayInfo.logicalHeight => pillar box
@@ -675,16 +729,16 @@ final class LogicalDisplay {
// comparing them.
int displayRectWidth, displayRectHeight;
if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
- displayRectWidth = displayInfo.logicalWidth;
- displayRectHeight = displayInfo.logicalHeight;
- } else if (physWidth * displayInfo.logicalHeight
- < physHeight * displayInfo.logicalWidth) {
+ displayRectWidth = displayLogicalWidth;
+ displayRectHeight = displayLogicalHeight;
+ } else if (physWidth * displayLogicalHeight
+ < physHeight * displayLogicalWidth) {
// Letter box.
displayRectWidth = physWidth;
- displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+ displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
} else {
// Pillar box.
- displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+ displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
displayRectHeight = physHeight;
}
int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 3452e0f188c3..e092fdae7cc7 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1151,7 +1151,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
*/
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
- final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+ final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+ mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo info = display.getDisplayInfoLocked();
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 3c98ee453913..15ee9372b46a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@ public class DisplayManagerFlags {
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+ Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+ Flags::enablePixelAnisotropyCorrection
+ );
+
private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
Flags::sensorBasedBrightnessThrottling
@@ -259,6 +264,10 @@ public class DisplayManagerFlags {
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+ return mPixelAnisotropyCorrectionEnabled.isEnabled();
+ }
+
public boolean isSensorBasedBrightnessThrottlingEnabled() {
return mSensorBasedBrightnessThrottling.isEnabled();
}
@@ -290,6 +299,7 @@ public class DisplayManagerFlags {
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mRefactorDisplayPowerController);
}
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 34045273c93a..9bf36e4e605f 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
@@ -186,6 +186,14 @@ flag {
}
flag {
+ name: "enable_pixel_anisotropy_correction"
+ namespace: "display_manager"
+ description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+ bug: "317363416"
+ is_fixed_read_only: true
+}
+
+flag {
name: "sensor_based_brightness_throttling"
namespace: "display_manager"
description: "Feature flag for enabling brightness throttling using sensor from config."
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 0ef23e903b6a..3fafca8ed377 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
@@ -133,7 +132,11 @@ public class GrammaticalInflectionService extends SystemService {
@Override
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- return canGetSystemGrammaticalGender(attributionSource)
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ throw new SecurityException("AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ }
+ return checkSystemTermsOfAddressIsEnabled()
? GrammaticalInflectionService.this.getSystemGrammaticalGender(
attributionSource, userId)
: GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -263,7 +266,11 @@ public class GrammaticalInflectionService extends SystemService {
throw new RuntimeException(e);
}
}
+ updateConfiguration(grammaticalGender, userId);
+ Trace.endSection();
+ }
+ private void updateConfiguration(int grammaticalGender, int userId) {
try {
Configuration config = new Configuration();
int preValue = config.getGrammaticalGender();
@@ -277,7 +284,6 @@ public class GrammaticalInflectionService extends SystemService {
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
- Trace.endSection();
}
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -369,7 +375,9 @@ public class GrammaticalInflectionService extends SystemService {
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ int grammaticalGender = getGrammaticalGenderFromXml(parser);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ updateConfiguration(grammaticalGender, userId);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse XML configuration from " + file, e);
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da2fc51..dd6433d98553 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@ package com.android.server.inputmethod;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@ final class AdditionalSubtypeMapRepository {
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@ final class AdditionalSubtypeMapRepository {
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 996477d9c9ba..dd2474bc0553 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -196,21 +195,16 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -290,6 +284,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final Context mContext;
final Resources mRes;
private final Handler mHandler;
+
+ /**
+ * TODO(b/329163064): Remove this field.
+ */
@NonNull
@MultiUserUnawareField
private InputMethodSettings mSettings;
@@ -730,344 +728,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
new CopyOnWriteArrayList<>();
- /**
- * Internal state snapshot when
- * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
- *
- * <p>Calling that IPC endpoint basically means that
- * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
- * back in the current IME process shortly, which will also affect what the current IME starts
- * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
- * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
- * logical input session between the client application and the current IME.</p>
- *
- * <p>Be careful to not keep strong references to this object forever, which can prevent
- * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
- * </p>
- */
- private static class StartInputInfo {
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- final int mSequenceNumber;
- final long mTimestamp;
- final long mWallTime;
- @UserIdInt
- final int mImeUserId;
- @NonNull
- final IBinder mImeToken;
- final int mImeDisplayId;
- @NonNull
- final String mImeId;
- @StartInputReason
- final int mStartInputReason;
- final boolean mRestarting;
- @UserIdInt
- final int mTargetUserId;
- final int mTargetDisplayId;
- @Nullable
- final IBinder mTargetWindow;
- @NonNull
- final EditorInfo mEditorInfo;
- @SoftInputModeFlags
- final int mTargetWindowSoftInputMode;
- final int mClientBindSequenceNumber;
-
- StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
- @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
- @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
- @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
- int clientBindSequenceNumber) {
- mSequenceNumber = sSequenceNumber.getAndIncrement();
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mImeUserId = imeUserId;
- mImeToken = imeToken;
- mImeDisplayId = imeDisplayId;
- mImeId = imeId;
- mStartInputReason = startInputReason;
- mRestarting = restarting;
- mTargetUserId = targetUserId;
- mTargetDisplayId = targetDisplayId;
- mTargetWindow = targetWindow;
- mEditorInfo = editorInfo;
- mTargetWindowSoftInputMode = targetWindowSoftInputMode;
- mClientBindSequenceNumber = clientBindSequenceNumber;
- }
- }
-
@GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
- @VisibleForTesting
- static final class SoftInputShowHideHistory {
- private final Entry[] mEntries = new Entry[16];
- private int mNextIndex = 0;
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- static final class Entry {
- final int mSequenceNumber = sSequenceNumber.getAndIncrement();
- @Nullable
- final ClientState mClientState;
- @SoftInputModeFlags
- final int mFocusedWindowSoftInputMode;
- @SoftInputShowHideReason
- final int mReason;
- // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
- final long mTimestamp;
- final long mWallTime;
- final boolean mInFullscreenMode;
- @NonNull
- final String mFocusedWindowName;
- @Nullable
- final EditorInfo mEditorInfo;
- @NonNull
- final String mRequestWindowName;
- @Nullable
- final String mImeControlTargetName;
- @Nullable
- final String mImeTargetNameFromWm;
- @Nullable
- final String mImeSurfaceParentName;
-
- Entry(ClientState client, EditorInfo editorInfo,
- String focusedWindowName, @SoftInputModeFlags int softInputMode,
- @SoftInputShowHideReason int reason,
- boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
- mClientState = client;
- mEditorInfo = editorInfo;
- mFocusedWindowName = focusedWindowName;
- mFocusedWindowSoftInputMode = softInputMode;
- mReason = reason;
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mInFullscreenMode = inFullscreenMode;
- mRequestWindowName = requestWindowName;
- mImeControlTargetName = imeControlTargetName;
- mImeTargetNameFromWm = imeTargetName;
- mImeSurfaceParentName = imeSurfaceParentName;
- }
- }
-
- void addEntry(@NonNull Entry entry) {
- final int index = mNextIndex;
- mEntries[index] = entry;
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")");
-
- pw.print(prefix);
- pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
- entry.mReason));
- pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
- pw.print(prefix);
- pw.println(" requestClient=" + entry.mClientState);
-
- pw.print(prefix);
- pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
-
- pw.print(prefix);
- pw.println(" requestWindowName=" + entry.mRequestWindowName);
-
- pw.print(prefix);
- pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
-
- pw.print(prefix);
- pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
- pw.print(prefix);
- pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
- pw.print(prefix);
- pw.print(" editorInfo:");
- if (entry.mEditorInfo != null) {
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
- } else {
- pw.println(" null");
- }
-
- pw.print(prefix);
- pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mFocusedWindowSoftInputMode));
- }
- }
- }
-
- /**
- * A ring buffer to store the history of {@link StartInputInfo}.
- */
- private static final class StartInputHistory {
- /**
- * Entry size for non low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
- /**
- * Entry size for low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
- private static int getEntrySize() {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
- } else {
- return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
- }
- }
-
- /**
- * Backing store for the ring buffer.
- */
- private final Entry[] mEntries = new Entry[getEntrySize()];
-
- /**
- * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
- * write.
- */
- private int mNextIndex = 0;
-
- /**
- * Recyclable entry to store the information in {@link StartInputInfo}.
- */
- private static final class Entry {
- int mSequenceNumber;
- long mTimestamp;
- long mWallTime;
- @UserIdInt
- int mImeUserId;
- @NonNull
- String mImeTokenString;
- int mImeDisplayId;
- @NonNull
- String mImeId;
- @StartInputReason
- int mStartInputReason;
- boolean mRestarting;
- @UserIdInt
- int mTargetUserId;
- int mTargetDisplayId;
- @NonNull
- String mTargetWindowString;
- @NonNull
- EditorInfo mEditorInfo;
- @SoftInputModeFlags
- int mTargetWindowSoftInputMode;
- int mClientBindSequenceNumber;
-
- Entry(@NonNull StartInputInfo original) {
- set(original);
- }
-
- void set(@NonNull StartInputInfo original) {
- mSequenceNumber = original.mSequenceNumber;
- mTimestamp = original.mTimestamp;
- mWallTime = original.mWallTime;
- mImeUserId = original.mImeUserId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mImeTokenString = String.valueOf(original.mImeToken);
- mImeDisplayId = original.mImeDisplayId;
- mImeId = original.mImeId;
- mStartInputReason = original.mStartInputReason;
- mRestarting = original.mRestarting;
- mTargetUserId = original.mTargetUserId;
- mTargetDisplayId = original.mTargetDisplayId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mTargetWindowString = String.valueOf(original.mTargetWindow);
- mEditorInfo = original.mEditorInfo;
- mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
- mClientBindSequenceNumber = original.mClientBindSequenceNumber;
- }
- }
-
- /**
- * Add a new entry and discard the oldest entry as needed.
- * @param info {@link StartInputInfo} to be added.
- */
- void addEntry(@NonNull StartInputInfo info) {
- final int index = mNextIndex;
- if (mEntries[index] == null) {
- mEntries[index] = new Entry(info);
- } else {
- mEntries[index].set(info);
- }
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")"
- + " reason="
- + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
- + " restarting=" + entry.mRestarting);
-
- pw.print(prefix);
- pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
- pw.print(" imeUserId=" + entry.mImeUserId);
- pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
- pw.print(prefix);
- pw.println(" targetWin=" + entry.mTargetWindowString
- + " [" + entry.mEditorInfo.packageName + "]"
- + " targetUserId=" + entry.mTargetUserId
- + " targetDisplayId=" + entry.mTargetDisplayId
- + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
- pw.print(prefix);
- pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mTargetWindowSoftInputMode));
-
- pw.print(prefix);
- pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
- + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
- + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
- + " fieldName=" + entry.mEditorInfo.fieldName
- + " actionId=" + entry.mEditorInfo.actionId
- + " actionLabel=" + entry.mEditorInfo.actionLabel);
- }
- }
- }
-
@GuardedBy("ImfLock.class")
@NonNull
private final StartInputHistory mStartInputHistory = new StartInputHistory();
@@ -1208,7 +871,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!mSystemReady) {
return;
}
- buildInputMethodListLocked(true);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ if (userId == mSettings.getUserId()) {
+ mSettings = settings;
+ }
+ }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
@@ -1404,13 +1078,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final boolean isCurrentUser = (userId == mSettings.getUserId());
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1454,13 +1122,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
-
- if (!isCurrentUser
- || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
+ return;
+ }
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1612,17 +1286,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+ + mSettings.getUserId());
}
- if (userId != currentUserId) {
+ if (!mSystemReady) {
return;
}
- mSettings = InputMethodSettings.createEmptyMap(userId);
- if (mSystemReady) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mSettings.getUserId() == userId) {
+ mSettings = newSettings;
// We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
@@ -1688,12 +1365,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
final int userId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ mSettings = InputMethodSettingsRepository.get(userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1856,7 +1534,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// The mSystemReady flag is set during boot phase,
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
+
+ final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
@@ -1937,7 +1618,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final String defaultImiId = mSettings.getSelectedInputMethod();
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
@@ -2044,9 +1731,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -2070,9 +1755,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -2096,7 +1780,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
methodList = mSettings.getEnabledInputMethodList();
settings = mSettings;
} else {
- settings = queryMethodMapForUserLocked(userId);
+ settings = InputMethodSettingsRepository.get(userId);
methodList = settings.getEnabledInputMethodList();
}
// filter caller's access to input methods
@@ -2156,22 +1840,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -4337,8 +4006,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return mSettings.getLastInputMethodSubtype();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4370,19 +4038,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4412,8 +4082,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
try {
synchronized (ImfLock.class) {
final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -5310,7 +4979,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -5322,10 +4991,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
-
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5596,8 +5261,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5659,27 +5324,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
if (userId == mSettings.getUserId()) {
if (!mSettings.getMethodMap().containsKey(imeId)
@@ -5690,7 +5339,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5830,7 +5479,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
@@ -6584,7 +6233,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6718,10 +6367,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
nextIme = mSettings.getSelectedInputMethod();
nextEnabledImes = mSettings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(userId);
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 000000000000..60b9a4cfe840
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 000000000000..3023603dc437
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private final Entry[] mEntries = new Entry[16];
+ private int mNextIndex = 0;
+
+ static final class Entry {
+ final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ @Nullable
+ final ClientState mClientState;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+ @SoftInputShowHideReason
+ final int mReason;
+ // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+ final long mTimestamp;
+ final long mWallTime;
+ final boolean mInFullscreenMode;
+ @NonNull
+ final String mFocusedWindowName;
+ @Nullable
+ final EditorInfo mEditorInfo;
+ @NonNull
+ final String mRequestWindowName;
+ @Nullable
+ final String mImeControlTargetName;
+ @Nullable
+ final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
+
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
+ boolean inFullscreenMode, String requestWindowName,
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
+ mClientState = client;
+ mEditorInfo = editorInfo;
+ mFocusedWindowName = focusedWindowName;
+ mFocusedWindowSoftInputMode = softInputMode;
+ mReason = reason;
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mInFullscreenMode = inFullscreenMode;
+ mRequestWindowName = requestWindowName;
+ mImeControlTargetName = imeControlTargetName;
+ mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
+ }
+ }
+
+ void addEntry(@NonNull Entry entry) {
+ final int index = mNextIndex;
+ mEntries[index] = entry;
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")");
+
+ pw.print(prefix);
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
+ entry.mReason));
+ pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+ pw.print(prefix);
+ pw.println(" requestClient=" + entry.mClientState);
+
+ pw.print(prefix);
+ pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
+
+ pw.print(prefix);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
+
+ pw.print(prefix);
+ pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
+
+ pw.print(prefix);
+ pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+ pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
+ pw.print(" editorInfo:");
+ if (entry.mEditorInfo != null) {
+ pw.print(" inputType=" + entry.mEditorInfo.inputType);
+ pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+ pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ } else {
+ pw.println(" null");
+ }
+
+ pw.print(prefix);
+ pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mFocusedWindowSoftInputMode));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 000000000000..3a39434f2d02
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+ /**
+ * Entry size for low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+ private static int getEntrySize() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+ } else {
+ return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+ }
+ }
+
+ /**
+ * Backing store for the ring buffer.
+ */
+ private final Entry[] mEntries = new Entry[getEntrySize()];
+
+ /**
+ * An index of {@link #mEntries}, to which next
+ * {@link #addEntry(StartInputInfo)} should
+ * write.
+ */
+ private int mNextIndex = 0;
+
+ /**
+ * Recyclable entry to store the information in {@link StartInputInfo}.
+ */
+ private static final class Entry {
+ int mSequenceNumber;
+ long mTimestamp;
+ long mWallTime;
+ @UserIdInt
+ int mImeUserId;
+ @NonNull
+ String mImeTokenString;
+ int mImeDisplayId;
+ @NonNull
+ String mImeId;
+ @StartInputReason
+ int mStartInputReason;
+ boolean mRestarting;
+ @UserIdInt
+ int mTargetUserId;
+ int mTargetDisplayId;
+ @NonNull
+ String mTargetWindowString;
+ @NonNull
+ EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ int mTargetWindowSoftInputMode;
+ int mClientBindSequenceNumber;
+
+ Entry(@NonNull StartInputInfo original) {
+ set(original);
+ }
+
+ void set(@NonNull StartInputInfo original) {
+ mSequenceNumber = original.mSequenceNumber;
+ mTimestamp = original.mTimestamp;
+ mWallTime = original.mWallTime;
+ mImeUserId = original.mImeUserId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mImeTokenString = String.valueOf(original.mImeToken);
+ mImeDisplayId = original.mImeDisplayId;
+ mImeId = original.mImeId;
+ mStartInputReason = original.mStartInputReason;
+ mRestarting = original.mRestarting;
+ mTargetUserId = original.mTargetUserId;
+ mTargetDisplayId = original.mTargetDisplayId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mTargetWindowString = String.valueOf(original.mTargetWindow);
+ mEditorInfo = original.mEditorInfo;
+ mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+ mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+ }
+ }
+
+ /**
+ * Add a new entry and discard the oldest entry as needed.
+ *
+ * @param info {@link StartInputInfo} to be added.
+ */
+ void addEntry(@NonNull StartInputInfo info) {
+ final int index = mNextIndex;
+ if (mEntries[index] == null) {
+ mEntries[index] = new Entry(info);
+ } else {
+ mEntries[index].set(info);
+ }
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")"
+ + " reason="
+ + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+ + " restarting=" + entry.mRestarting);
+
+ pw.print(prefix);
+ pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+ pw.print(" imeUserId=" + entry.mImeUserId);
+ pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+ pw.print(prefix);
+ pw.println(" targetWin=" + entry.mTargetWindowString
+ + " [" + entry.mEditorInfo.packageName + "]"
+ + " targetUserId=" + entry.mTargetUserId
+ + " targetDisplayId=" + entry.mTargetDisplayId
+ + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+ pw.print(prefix);
+ pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mTargetWindowSoftInputMode));
+
+ pw.print(prefix);
+ pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+ + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+ + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+ + " fieldName=" + entry.mEditorInfo.fieldName
+ + " actionId=" + entry.mEditorInfo.actionId
+ + " actionLabel=" + entry.mEditorInfo.actionLabel);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 000000000000..1cff737d4a00
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ final int mSequenceNumber;
+ final long mTimestamp;
+ final long mWallTime;
+ @UserIdInt
+ final int mImeUserId;
+ @NonNull
+ final IBinder mImeToken;
+ final int mImeDisplayId;
+ @NonNull
+ final String mImeId;
+ @StartInputReason
+ final int mStartInputReason;
+ final boolean mRestarting;
+ @UserIdInt
+ final int mTargetUserId;
+ final int mTargetDisplayId;
+ @Nullable
+ final IBinder mTargetWindow;
+ @NonNull
+ final EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mTargetWindowSoftInputMode;
+ final int mClientBindSequenceNumber;
+
+ StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+ @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+ @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+ @NonNull EditorInfo editorInfo,
+ @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+ int clientBindSequenceNumber) {
+ mSequenceNumber = sSequenceNumber.getAndIncrement();
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mImeUserId = imeUserId;
+ mImeToken = imeToken;
+ mImeDisplayId = imeDisplayId;
+ mImeId = imeId;
+ mStartInputReason = startInputReason;
+ mRestarting = restarting;
+ mTargetUserId = targetUserId;
+ mTargetDisplayId = targetDisplayId;
+ mTargetWindow = targetWindow;
+ mEditorInfo = editorInfo;
+ mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+ mClientBindSequenceNumber = clientBindSequenceNumber;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c38fbda4f5fd..e80c79a8cffb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -338,6 +338,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
@@ -740,7 +741,7 @@ public class NotificationManagerService extends SystemService {
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
- static final IBinder ALLOWLIST_TOKEN = new Binder();
+ private static final IBinder ALLOWLIST_TOKEN = new Binder();
protected RankingHandler mRankingHandler;
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -1825,6 +1826,12 @@ public class NotificationManagerService extends SystemService {
}
}
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted,
+ boolean hasSensitiveContent, int lifespanMs) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted,
+ hasSensitiveContent, lifespanMs);
+ }
+
@GuardedBy("mNotificationLock")
void clearSoundLocked() {
mSoundNotificationKey = null;
@@ -4868,7 +4875,7 @@ public class NotificationManagerService extends SystemService {
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.overrideAllowlistToken(null);
+ notification.clearAllowlistToken();
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -6384,7 +6391,7 @@ public class NotificationManagerService extends SystemService {
if (Objects.equals(adjustment.getKey(), r.getKey())
&& Objects.equals(adjustment.getUser(), r.getUserId())
&& mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, false);
r.applyAdjustments();
// importance is checked at the beginning of the
// PostNotificationRunnable, before the signal extractors are run, so
@@ -6394,7 +6401,7 @@ public class NotificationManagerService extends SystemService {
}
}
if (!foundEnqueued) {
- applyAdjustmentFromAssistant(token, adjustment);
+ applyAdjustmentsFromAssistant(token, List.of(adjustment));
}
}
} finally {
@@ -6422,7 +6429,7 @@ public class NotificationManagerService extends SystemService {
for (Adjustment adjustment : adjustments) {
NotificationRecord r = mNotificationsByKey.get(adjustment.getKey());
if (r != null && mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, true);
// If the assistant has blocked the notification, cancel it
// This will trigger a sort, so we don't have to explicitly ask for
// one here.
@@ -6706,7 +6713,9 @@ public class NotificationManagerService extends SystemService {
}
}
- private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
+ @GuardedBy("mNotificationLock")
+ private void applyAdjustmentLocked(NotificationRecord r, Adjustment adjustment,
+ boolean isPosted) {
if (r == null) {
return;
}
@@ -6723,6 +6732,11 @@ public class NotificationManagerService extends SystemService {
adjustments.remove(removeKey);
}
r.addAdjustment(adjustment);
+ if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
+ logSensitiveAdjustmentReceived(isPosted,
+ adjustment.getSignals().getBoolean(Adjustment.KEY_SENSITIVE_CONTENT),
+ r.getLifespanMs(System.currentTimeMillis()));
+ }
}
}
@@ -7870,8 +7884,6 @@ public class NotificationManagerService extends SystemService {
}
}
- notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9a8541..81f11b52dcb7 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.server.ondeviceintelligence;
-/**
- * @hide
- */
-parcelable Content;
+public interface OnDeviceIntelligenceManagerInternal {
+ String getRemoteServicePackageName();
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 71800efae292..28682e3d916f 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,12 +16,11 @@
package com.android.server.ondeviceintelligence;
-import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
-
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppGlobals;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -33,25 +32,32 @@ import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.ICancellationSignal;
+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.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.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
@@ -62,8 +68,10 @@ 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.LocalServices;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +92,9 @@ 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;
+
/** 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";
@@ -96,19 +107,30 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
+ @GuardedBy("mLock")
+ private String[] mTemporaryServiceNames;
+
+ /**
+ * Handler used to reset the temporary service names.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
public OnDeviceIntelligenceManagerService(Context context) {
super(context);
mContext = context;
+ mTemporaryServiceNames = new String[0];
}
@Override
public void onStart() {
publishBinderService(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
/* allowIsolated = */true);
+ LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
+ OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName);
}
-
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -133,195 +155,211 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
- private final class OnDeviceIntelligenceManagerInternal extends
- IOnDeviceIntelligenceManager.Stub {
- @Override
- public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
- Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- remoteCallback.sendResult(null);
- return;
+ private IBinder getOnDeviceIntelligenceManagerService() {
+ return new IOnDeviceIntelligenceManager.Stub() {
+ @Override
+ public String getRemoteServicePackageName() {
+ return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getVersion(remoteCallback));
- }
- @Override
- public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(featureCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getVersion(remoteCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
- }
- @Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- listFeaturesCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
- }
- @Override
- public void getFeatureDetails(Feature feature,
- IFeatureDetailsCallback featureDetailsCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureDetailsCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.listFeatures(Binder.getCallingUid(),
+ listFeaturesCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
- featureDetailsCallback));
- }
- @Override
- public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
- IDownloadCallback downloadCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingOrSelfPermission(
- 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",
- new PersistableBundle());
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
+ downloadCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
- cancellationSignal,
- downloadCallback));
- }
- @Override
- public void requestTokenInfo(Feature feature,
- Content request, ICancellationSignal cancellationSignal,
- ITokenInfoCallback tokenInfoCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
- Objects.requireNonNull(tokenInfoCallback);
-
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- tokenInfoCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestTokenInfo(Feature feature,
+ Bundle request, ICancellationSignal cancellationSignal,
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenInfoCallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteInferenceService.run(
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature,
+ request,
+ cancellationSignal,
+ tokenInfoCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
- cancellationSignal,
- tokenInfoCallback));
- }
- @Override
- public void processRequest(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IResponseCallback responseCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(responseCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- responseCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void processRequest(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteInferenceService.run(
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequest(Binder.getCallingUid(), feature, request,
- requestType,
- cancellationSignal, processingSignal,
- responseCallback));
- }
- @Override
- public void processRequestStreaming(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IStreamingResponseCallback streamingCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- streamingCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ 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();
+ mRemoteInferenceService.run(
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
- request, requestType,
- cancellationSignal, processingSignal,
- streamingCallback));
- }
+
+ @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() throws RemoteException {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService);
+ String serviceName = getServiceNames()[0];
validateService(serviceName, false);
mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -352,13 +390,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
IProcessingUpdateStatusCallback callback) {
try {
ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
+ mRemoteInferenceService.run(
service -> service.updateProcessingState(
processingState, callback));
} catch (RemoteException unused) {
try {
callback.onFailure(
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+ OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
"Received failure invoking the remote processing service.");
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to send failure status.", ex);
@@ -371,8 +409,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
+ String serviceName = getServiceNames()[1];
validateService(serviceName, true);
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -384,7 +421,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
@@ -404,7 +441,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
filePath, future));
}
@@ -413,7 +450,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature, remoteCallback));
}
@@ -469,4 +506,92 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+
+ @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)};
+ }
+
+ @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 (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+
+ if (durationMs != -1) {
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ }
+ }
+ }
+
+ 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");
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 000000000000..a76d8a31405d
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,96 @@
+/*
+ * 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.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();
+ 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.");
+ }
+
+ private int setTemporaryServices() {
+ final PrintWriter out = getOutPrintWriter();
+ final String intelligenceServiceName = getNextArg();
+ final String inferenceServiceName = getNextArg();
+ if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+ && inferenceServiceName == null) {
+ 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;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/permission/OWNERS b/services/core/java/com/android/server/permission/OWNERS
new file mode 100644
index 000000000000..fb6099cf7e5a
--- /dev/null
+++ b/services/core/java/com/android/server/permission/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/permission/PermissionManagerLocal.java b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
new file mode 100644
index 000000000000..7251e6ee62de
--- /dev/null
+++ b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission;
+
+import android.annotation.TestApi;
+import com.android.internal.annotations.Keep;
+
+/**
+ * In-process API for server side permission related infrastructure.
+ *
+ * @hide
+ */
+@Keep
+@TestApi
+public interface PermissionManagerLocal {
+
+ /**
+ * Get whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @return whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ boolean isSignaturePermissionAllowlistForceEnforced();
+
+ /**
+ * Set whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @param forceEnforced whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ void setSignaturePermissionAllowlistForceEnforced(boolean forceEnforced);
+}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c2667b..c6bb99eed7ee 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@ public class LauncherAppsService extends SystemService {
@RequiresPermission(READ_FRAME_BUFFER)
@Override
+ public void saveViewCaptureData() {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+ } else {
+ Log.w(TAG, "caller lacks permissions to save view capture data");
+ }
+ }
+
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
public void registerDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ba903789e759..9e4ea6a74da4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -964,13 +964,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
- if (ps == null || ps.getPkg() == null) {
+ if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
return false;
}
+ int uid = UserHandle.getUid(userId, ps.getAppId());
String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
if (emergencyInstaller == null || !ArrayUtils.contains(
- snapshot.getPackagesForUid(mInstallerUid),
- emergencyInstaller)) {
+ snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) {
+ return false;
+ }
+ // Only system installers can have an emergency installer
+ if (PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)
+ && PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES, uid)
+ && PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_SELF_UPDATES, uid)) {
return false;
}
return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 6266ef325e53..4ed6e808d23f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,6 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.SigningDetails;
import android.os.Binder;
import android.os.UserHandle;
@@ -124,6 +126,48 @@ public interface PackageManagerLocal {
FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
/**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails);
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails);
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void clearOverrideSigningDetails();
+
+ /**
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8895cb..c9fd2610bfb7 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
UserManager.DISALLOW_SIM_GLOBALLY,
- UserManager.DISALLOW_ASSIST_CONTENT
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ UserManager.DISALLOW_THREAD_NETWORK
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
@@ -252,7 +254,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 8d05450147e2..55afb17614af 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -20,9 +20,12 @@ import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
import android.os.Binder;
+import android.os.Build;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.apk.ApkSignatureVerifier;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
@@ -72,6 +75,31 @@ public class PackageManagerLocalImpl implements PackageManagerLocal {
mService.snapshotComputer(false /*allowLiveComputer*/), null);
}
+ @Override
+ public void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.addOverrideSigningDetails(oldSigningDetails, newSigningDetails);
+ }
+
+ @Override
+ public void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.removeOverrideSigningDetails(oldSigningDetails);
+ }
+
+ @Override
+ public void clearOverrideSigningDetails() {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.clearOverrideSigningDetails();
+ }
+
private abstract static class BaseSnapshotImpl implements AutoCloseable {
private boolean mClosed;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5974ac8bd41b..266418fd5b4a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4315,6 +4315,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean allowDuringSetup) {
if (allowDuringSetup || isUserSetupComplete()) {
mContext.startActivityAsUser(intent, bundle, handle);
+ dismissKeyboardShortcutsMenu();
} else {
Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
}
@@ -4365,6 +4366,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (statusbar != null) {
statusbar.showRecentApps(triggeredFromAltTab);
}
+ dismissKeyboardShortcutsMenu();
}
private void toggleKeyboardShortcutsMenu(int deviceId) {
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index daa02111f71f..8337fd2453df 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS \ No newline at end of file
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65283dc..65ab12930c59 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@ public class WallpaperDataParser {
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
+ Rect legacyCropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
Rect totalCropHint = new Rect(
getAttributeInt(parser, "totalCropLeft", 0),
getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@ public class WallpaperDataParser {
parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
}
- if (wallpaper.mCropHints.size() == 0) {
+ if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- // use the old attributes to find the crop for one screen orientation.
- Integer orientation = totalCropHint.width() < totalCropHint.height()
+ int orientation = legacyCropHint.width() < legacyCropHint.height()
? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
- if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ if (!legacyCropHint.isEmpty()) {
+ wallpaper.mCropHints.put(orientation, legacyCropHint);
+ }
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
- wallpaper.cropHint.set(totalCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb14f223..885baf65013f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+ if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
wallpaper.mWallpaperDimAmount = maxDimAmount;
// Also set the dim amount to the lock screen wallpaper if the lock and home screen
// do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd1e4e8..6fa6957f2949 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7786,8 +7786,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ final boolean show = (isVisible()
+ // Ensure that the activity content is hidden when the decor surface is boosted to
+ // prevent UI redressing attack.
+ && !getTask().isDecorSurfaceBoosted())
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2fc6b5f5ca9a..963b4cba5069 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1478,7 +1478,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
mLaunchingActivityWakeLock.release();
}
- mRootWindowContainer.ensureActivitiesVisible();
}
// Atomically retrieve all of the other things to do.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f40342461..f7baa7914f86 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,10 +18,10 @@ package com.android.server.wm;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -37,12 +37,11 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balShowToasts;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -752,7 +751,6 @@ public class BackgroundActivityStartController {
Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)!" + state.dump());
- showBalRiskToast();
return allowBasedOnCaller(state);
}
}
@@ -762,7 +760,6 @@ public class BackgroundActivityStartController {
Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)!" + state.dump());
- showBalRiskToast();
return allowBasedOnRealCaller(state);
}
}
@@ -793,7 +790,11 @@ public class BackgroundActivityStartController {
private BalVerdict abortLaunch(BalState state) {
Slog.wtf(TAG, "Background activity launch blocked! "
+ state.dump());
- showBalBlockedToast();
+ if (balShowToastsBlocked()
+ && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
+ // only show a toast if either caller or real caller could launch if they opted in
+ showToast("BAL blocked. go/debug-bal");
+ }
return statsLog(BalVerdict.BLOCK, state);
}
@@ -1192,18 +1193,6 @@ public class BackgroundActivityStartController {
return true;
}
- private void showBalBlockedToast() {
- if (balShowToastsBlocked()) {
- showToast("BAL blocked. go/debug-bal");
- }
- }
-
- private void showBalRiskToast() {
- if (balShowToasts()) {
- showToast("BAL allowed in compat mode. go/debug-bal");
- }
- }
-
@VisibleForTesting void showToast(String toastText) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a914c0712319..b616d24cfebb 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@ final class ContentRecorder implements WindowContainerListener {
ContentRecorder(@NonNull DisplayContent displayContent) {
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
- new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+ && !new DisplayManagerFlags()
+ .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 877378cac06a..a29cb60ff545 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -173,18 +173,25 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
mDisplayContent.mInitialDisplayHeight);
final int fromRotation = mDisplayContent.getRotation();
- onStartCollect.run();
-
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
-
- if (physicalDisplayUpdated) {
- onDisplayUpdated(transition, fromRotation, startBounds);
- } else {
- final TransitionRequestInfo.DisplayChange displayChange =
- getCurrentDisplayChange(fromRotation, startBounds);
- mDisplayContent.mTransitionController.requestStartTransition(transition,
- /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ mDisplayContent.mAtmService.deferWindowLayout();
+ try {
+ onStartCollect.run();
+
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+
+ if (physicalDisplayUpdated) {
+ onDisplayUpdated(transition, fromRotation, startBounds);
+ } else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ }
+ } finally {
+ // Run surface placement after requestStartTransition, so shell side can receive
+ // the transition request before handling task info changes.
+ mDisplayContent.mAtmService.continueWindowLayout();
}
});
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 282ecc77bd50..837d08b33756 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6210,7 +6210,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* @param onDisplayChangeApplied callback that is called when the changes are applied
*/
void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
- mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ mAtmService.deferWindowLayout();
+ try {
+ mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
}
void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d87e21c0ac92..55dc30cc37d5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3741,7 +3741,9 @@ class Task extends TaskFragment {
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
// Place the decor surface under any untrusted content.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& shouldPlaceDecorSurfaceBelowContainer(wc)) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@ class Task extends TaskFragment {
}
// Place the decor surface just above the owner TaskFragment.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@ class Task extends TaskFragment {
}
}
- // If not placed yet, the decor surface should be on top of all non-boosted children.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+ // Boost the decor surface above other non-boosted windows if requested. The cover surface
+ // will ensure that the content of the windows below are invisible.
+ if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
mDecorSurfaceContainer.assignLayer(t, layer++);
- decorSurfacePlaced = true;
}
for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@ class Task extends TaskFragment {
return !isOwnActivity && !isTrustedTaskFragment;
}
+ void setDecorSurfaceBoosted(
+ @NonNull TaskFragment ownerTaskFragment,
+ boolean isBoosted,
+ @Nullable SurfaceControl.Transaction clientTransaction) {
+ if (mDecorSurfaceContainer == null
+ || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+ return;
+ }
+ mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+ // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+ // surface visibility is updated with prepareSurfaces()
+ assignChildLayers();
+ }
+
+ boolean isDecorSurfaceBoosted() {
+ return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
@@ -6796,14 +6818,35 @@ class Task extends TaskFragment {
}
/**
- * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
- * below children windows except for own Activities and TaskFragment in fully trusted mode.
+ * A class managing the decor surface.
+ *
+ * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+ * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+ * decor surface is created and shared with the client app with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+ * be removed with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+ *
+ * When boosted with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+ * surface is placed above all non-boosted windows in the Task, but all the content below it
+ * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+ * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+ * dragging to indicate new bounds.
*/
@VisibleForTesting
class DecorSurfaceContainer {
+
+ // The container surface is the parent of the decor surface. The container surface
+ // should NEVER be shared with the client. It is used to ensure that the decor surface has
+ // a z-order in the Task that is managed by WM core and cannot be updated by the client
+ // process.
@VisibleForTesting
@NonNull final SurfaceControl mContainerSurface;
+ // The decor surface is shared with the client process owning the
+ // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+ // or other decorations.
@VisibleForTesting
@NonNull final SurfaceControl mDecorSurface;
@@ -6812,12 +6855,18 @@ class Task extends TaskFragment {
@VisibleForTesting
@NonNull TaskFragment mOwnerTaskFragment;
+ private boolean mIsBoosted;
+
+ // The surface transactions that will be applied when the layer is reassigned.
+ @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+ new ArrayList<>();
+
private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
mOwnerTaskFragment = initialOwner;
mContainerSurface = makeSurface().setContainerLayer()
.setParent(mSurfaceControl)
.setName(mSurfaceControl + " - decor surface container")
- .setEffectLayer()
+ .setContainerLayer()
.setHidden(false)
.setCallsite("Task.DecorSurfaceContainer")
.build();
@@ -6830,14 +6879,28 @@ class Task extends TaskFragment {
.build();
}
+ private void setBoosted(
+ boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+ mIsBoosted = isBoosted;
+ // The client transaction will be applied together with the next assignLayer.
+ if (clientTransaction != null) {
+ mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ }
+ }
+
private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
t.setLayer(mContainerSurface, layer);
t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+ for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+ t.merge(mPendingClientTransactions.get(i));
+ }
+ mPendingClientTransactions.clear();
}
private void release() {
- mDecorSurface.release();
- mContainerSurface.release();
+ getSyncTransaction()
+ .remove(mDecorSurface)
+ .remove(mContainerSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c91925081d8e..3cf561c1b62f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
} else {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
}
shouldSleep = false;
} else if (mPausingActivity != null) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index a84a99a9fbc5..74dad916c5c7 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -671,12 +671,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
if (hasImeSurface) {
if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null
&& dc.isFixedRotationLaunchingApp(topActivity)) {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+ removalInfo.deferRemoveMode = DEFER_MODE_ROTATION;
} else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+ removalInfo.deferRemoveMode = DEFER_MODE_NORMAL;
}
- } else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
}
final WindowState mainWindow =
@@ -745,6 +743,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
removalInfo.taskId = taskId;
removalInfo.windowlessSurface = true;
removalInfo.removeImmediately = immediately;
+ removalInfo.deferRemoveMode = DEFER_MODE_NONE;
try {
lastOrganizer.removeStartingWindow(removalInfo);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71ffabf20bb4..40b1b20909af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9098,7 +9098,7 @@ public class WindowManagerService extends IWindowManager.Stub
Objects.requireNonNull(outInputChannel);
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
- ? mInputToWindowMap.get(hostInputTransferToken.mToken) : null;
+ ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
hostWindowState, callingUid, callingPid, sanitizedType, displayId,
@@ -9127,12 +9127,13 @@ public class WindowManagerService extends IWindowManager.Stub
// If the transferToToken exists in the input to window map, it means the request
// is to transfer from embedded to host. Otherwise, the transferToToken
// represents an embedded window so transfer from host to embedded.
- WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken);
+ WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
windowStateTo);
} else {
- WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken);
+ WindowState windowStateFrom = mInputToWindowMap.get(
+ transferFromToken.getToken());
didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
transferToToken);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106beb55..7e6f5ac7497e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.moveOrCreateDecorSurfaceFor(taskFragment);
+ taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
break;
}
case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.removeDecorSurface();
+ taskFragment.getTask().removeDecorSurface();
break;
}
case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
operation.isMoveToBottomIfClearWhenLaunch());
break;
}
+ case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+ if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+ final SurfaceControl.Transaction clientTransaction =
+ operation.getSurfaceTransaction();
+ if (clientTransaction != null) {
+ // Sanitize the client transaction. sanitize() silently removes invalid
+ // operations and does not throw or provide signal about whether there are
+ // any invalid operations.
+ clientTransaction.sanitize(caller.mPid, caller.mUid);
+ }
+ taskFragment.getTask().setDecorSurfaceBoosted(
+ taskFragment,
+ operation.getBooleanValue() /* isBoosted */,
+ clientTransaction);
+ }
+ break;
+ }
}
return effects;
}
@@ -1616,19 +1633,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return false;
}
- // TODO (b/293654166) remove the decor surface checks once we clear security reviews
- if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
- || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
- + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
- }
-
if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398342dc..610fcb5962c8 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@ void NativeInputManager::dump(std::string& dump) {
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
- mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
if (auto pc = mLocked.legacyPointerController.lock(); pc) {
dump += pc->dump();
@@ -1717,7 +1717,7 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request)
return;
}
- ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
mLocked.pointerCaptureRequest = request;
} // release lock
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 96ef2ed61f1a..bdea4f9d2baa 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -568,7 +568,10 @@ public class MetricUtilities {
/* clicked_entries */ browsedClickedEntries,
/* provider_of_clicked_entry */ browsedProviderUid,
/* api_status */ apiStatus,
- /* primary_indicated */ finalPhaseMetric.isPrimary()
+ /* primary_indicated */ finalPhaseMetric.isPrimary(),
+ /* oem_credential_manager_ui_uid */ finalPhaseMetric.getOemUiUid(),
+ /* fallback_credential_manager_ui_uid */ finalPhaseMetric.getFallbackUiUid(),
+ /* oem_ui_usage_status */ finalPhaseMetric.getOemUiUsageStatus()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index cad9a09247e6..c5f292118030 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -182,7 +182,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
CredentialProviderInfo info,
String hybridService) {
Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
- if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+ if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
if (hybridComponentName != null && hybridComponentName
.equals(info.getComponentName())) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 468d3c80707c..9dd6db6f9d6a 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -67,6 +67,10 @@ public class ChosenProviderFinalPhaseMetric {
// Other General Information, such as final api status, provider status, entry info, etc...
+ private int mOemUiUid = -1;
+ private int mFallbackUiUid = -1;
+ private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
private int mChosenProviderStatus = -1;
// Indicates if an exception was thrown by this provider, false by default
private boolean mHasException = false;
@@ -302,4 +306,28 @@ public class ChosenProviderFinalPhaseMetric {
public boolean isPrimary() {
return mIsPrimary;
}
+
+ public void setOemUiUid(int oemUiUid) {
+ mOemUiUid = oemUiUid;
+ }
+
+ public int getOemUiUid() {
+ return mOemUiUid;
+ }
+
+ public void setFallbackUiUid(int fallbackUiUid) {
+ mFallbackUiUid = fallbackUiUid;
+ }
+
+ public int getFallbackUiUid() {
+ return mFallbackUiUid;
+ }
+
+ public void setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+ mOemUiUsageStatus = oemUiUsageStatus;
+ }
+
+ public int getOemUiUsageStatus() {
+ return mOemUiUsageStatus.getLoggingInt();
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
new file mode 100644
index 000000000000..2fd3a868369d
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+
+
+public enum OemUiUsageStatus {
+ UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
+ SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
+ FAILURE_NOT_SPECIFIED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED),
+ FAILURE_SPECIFIED_BUT_NOT_FOUND(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND),
+ FAILURE_SPECIFIED_BUT_NOT_ENABLED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED);
+
+ private final int mLoggingInt;
+
+ OemUiUsageStatus(int loggingInt) {
+ mLoggingInt = loggingInt;
+ }
+
+ public int getLoggingInt() {
+ return mLoggingInt;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c37946b4d750..38ab765154d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3395,6 +3395,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (shouldMigrateV1ToDevicePolicyEngine()) {
migrateV1PoliciesToDevicePolicyEngine();
}
+ maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
migratePoliciesToPolicyEngineLocked();
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
@@ -12301,9 +12302,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(admin);
- // Only allow the system user to use this method
- Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
- "createAndManageUser was called from non-system user");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
@@ -12314,6 +12312,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"createAndManageUser was called while in headless single user mode");
}
+ // Only allow the system user to use this method
+ Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+ "createAndManageUser was called from non-system user");
+
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
@@ -13177,27 +13179,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
boolean parent) {
synchronized (getLockObject()) {
+
+ int ownerType;
if (isDeviceOwner(caller)) {
- if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
- setGlobalUserRestrictionInternal(admin, key, enabled);
- } else {
- setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId());
- }
+ ownerType = OWNER_TYPE_DEVICE_OWNER;
+ } else if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
} else if (isProfileOwner(caller)) {
- if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
- || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)
- && UserRestrictionsUtils.isGlobal(
- OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
- setGlobalUserRestrictionInternal(admin, key, enabled);
- } else {
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
- }
+ ownerType = OWNER_TYPE_PROFILE_OWNER;
} else {
throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+ " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
}
+ setBackwardCompatibleUserRestrictionLocked(ownerType, admin, caller.getUserId(), key,
+ enabled, parent);
+ }
+ }
+
+ private void setBackwardCompatibleUserRestrictionLocked(
+ int ownerType, EnforcingAdmin admin, int userId, String key, boolean enabled,
+ boolean parent) {
+ if (ownerType == OWNER_TYPE_DEVICE_OWNER) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ setLocalUserRestrictionInternal(admin, key, enabled, userId);
+ }
+ } else if (ownerType == OWNER_TYPE_PROFILE_OWNER
+ || ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
+ || (parent && ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE
+ && UserRestrictionsUtils.isGlobal(
+ OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ int affectedUserId = parent
+ ? getProfileParentId(userId) : userId;
+ setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
+ }
+ } else {
+ throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+ + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
}
}
@@ -16877,6 +16899,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+ || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
? UserHandle.USER_SYSTEM
: callingUserId;
Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21573,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+
+
+ boolean isSingleUserMode;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ DeviceAdminInfo adminInfo = findAdmin(
+ deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+ isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ } else {
+ isSingleUserMode =
+ (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ }
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
- ? mUserManagerInternal.getMainUserId()
- : UserHandle.USER_SYSTEM;
+ && isSingleUserMode
+ ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
@@ -23732,11 +23767,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
- boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
- if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
- return false;
+ synchronized (getLockObject()) {
+ boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
+ if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
+ return false;
+ }
+ boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+ migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ return migrated;
}
- return migrateV1PoliciesToDevicePolicyEngine();
});
}
@@ -23765,6 +23804,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Migrates the initial set of policies to use policy engine.
+ * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+ * update from Android T to Android U
+ */
+ private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+ return;
+ }
+ migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ mOwners.markPostUpgradeMigration();
+ }
+
+ private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ try {
+ migrateScreenCapturePolicyLocked();
+ migrateLockTaskPolicyLocked();
+ migrateUserRestrictionsLocked();
+ return true;
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+ + "policy engine.");
+ return false;
+ }
+ }
+
+ /**
* @return {@code true} if policies were migrated successfully, {@code false} otherwise.
*/
private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23841,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
migrateAutoTimezonePolicy();
migratePermissionGrantStatePolicies();
}
- migrateScreenCapturePolicyLocked();
migratePermittedInputMethodsPolicyLocked();
migrateAccountManagementDisabledPolicyLocked();
migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23921,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void migrateScreenCapturePolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
- if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
- return;
- }
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
if (admin != null
&& ((isDeviceOwner(admin) && admin.disableScreenCapture)
|| (admin.getParentActiveAdmin() != null
&& admin.getParentActiveAdmin().disableScreenCapture))) {
+
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23955,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
}
+ private void migrateLockTaskPolicyLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null) {
+ int doUserId = deviceOwner.getUserHandle().getIdentifier();
+ DevicePolicyData policies = getUserData(doUserId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ // TODO: find out about persistent preferred activities
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+ }
+ }
+
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+ if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+ DevicePolicyData policies = getUserData(userId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+ }
+ }
+ }
+ });
+ }
+
+ private void setLockTaskPolicyInPolicyEngine(
+ ActiveAdmin admin, int userId, List<String> packages, int features) {
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ userId,
+ admin);
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ enforcingAdmin,
+ new LockTaskPolicy(new HashSet<>(packages), features),
+ userId);
+ }
+
private void migratePermittedInputMethodsPolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
@@ -23986,6 +24089,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
}
+ private void migrateUserRestrictionsLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo userInfo : users) {
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
+ if (admin == null) continue;
+ ComponentName adminComponent = admin.info.getComponent();
+ int userId = userInfo.id;
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ adminComponent,
+ userId,
+ admin);
+ int ownerType;
+ if (isDeviceOwner(admin)) {
+ ownerType = OWNER_TYPE_DEVICE_OWNER;
+ } else if (isProfileOwnerOfOrganizationOwnedDevice(adminComponent, userId)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
+ } else if (isProfileOwner(adminComponent, userId)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER;
+ } else {
+ throw new IllegalStateException("Invalid DO/PO state");
+ }
+
+ for (final String restriction : admin.ensureUserRestrictions().keySet()) {
+ setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+ restriction, /* enabled */ true, /* parent */ false);
+ }
+ for (final String restriction : admin.getParentActiveAdmin()
+ .ensureUserRestrictions().keySet()) {
+ setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+ restriction, /* enabled */ true, /* parent */ true);
+ }
+ }
+ });
+ }
+
private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
return mInjector.binderWithCleanCallingIdentity(() ->
mContext.getPackageManager().getInstalledPackagesAsUser(
@@ -24256,4 +24395,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mDevicePolicyEngine.getMaxPolicyStorageLimit();
}
+
+ @Override
+ public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+ caller.getUserId());
+
+ return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a98880ec84..7912cbce554f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@ class Owners {
}
}
+ void markPostUpgradeMigration() {
+ synchronized (mData) {
+ mData.mPoliciesMigratedPostUpdate = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
boolean isSecurityLoggingMigrated() {
synchronized (mData) {
return mData.mSecurityLoggingMigrated;
}
}
+ boolean isMigratedPostUpdate() {
+ synchronized (mData) {
+ return mData.mPoliciesMigratedPostUpdate;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0070c8..42ac998bf96c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@ class OwnersData {
private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
+ private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
// Internal state for the device owner package.
OwnerInfo mDeviceOwner;
int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@ class OwnersData {
boolean mMigratedToPolicyEngine = false;
boolean mSecurityLoggingMigrated = false;
+ boolean mPoliciesMigratedPostUpdate = false;
+
OwnersData(PolicyPathProvider pathProvider) {
mPathProvider = pathProvider;
}
@@ -400,6 +404,7 @@ class OwnersData {
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+ out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
@@ -463,8 +468,11 @@ class OwnersData {
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+ mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+ null, ATTR_MIGRATED_POST_UPGRADE, false);
mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab99fce..e713a827fc76 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@ final class PolicyDefinition<V> {
UserManager.DISALLOW_SIM_GLOBALLY,
POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ }
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73d830dcde33..c6189ed405a1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@ import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
@@ -1965,6 +1966,7 @@ public final class SystemServer implements Dumpable {
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startWearableSensingService(t);
+ startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -3335,6 +3337,12 @@ public final class SystemServer implements Dumpable {
t.traceEnd(); // startOtherServices
}
+ private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
+ t.traceBegin("startOnDeviceIntelligenceManagerService");
+ mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ t.traceEnd();
+ }
+
/**
* Starts system services defined in apexes.
*
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index acaec211440d..fd2e8c8fc9e7 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -27,9 +27,11 @@ import com.android.server.LocalServices
import com.android.server.SystemConfig
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.permission.PermissionManagerLocal
import com.android.server.permission.access.appop.AppOpService
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.PermissionManagerLocalImpl
import com.android.server.permission.access.permission.PermissionService
import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
@@ -63,6 +65,11 @@ class AccessCheckingService(context: Context) : SystemService(context) {
LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
+
+ LocalManagerRegistry.addManager(
+ PermissionManagerLocal::class.java,
+ PermissionManagerLocalImpl(this)
+ )
}
fun initialize() {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 67df67fdf6c1..af8ce31205bf 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -63,6 +63,12 @@ class AppIdPermissionPolicy : SchemePolicy() {
private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
+ /**
+ * Test-only switch to enforce signature permission allowlist even on debuggable builds.
+ */
+ @Volatile
+ var isSignaturePermissionAllowlistForceEnforced = false
+
override val subjectScheme: String
get() = UidUri.SCHEME
@@ -1274,7 +1280,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
SigningDetails.CertCapabilities.PERMISSION
)
if (!Flags.signaturePermissionAllowlistEnabled()) {
- return hasCommonSigner;
+ return hasCommonSigner
}
if (!hasCommonSigner) {
return false
@@ -1308,7 +1314,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
" ${packageState.packageName} (${packageState.path}) not in" +
" signature permission allowlist"
)
- if (!Build.isDebuggable()) {
+ if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
return false
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
new file mode 100644
index 000000000000..ad2d70bbe147
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
@@ -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.permission.access.permission
+
+import android.os.Build
+import com.android.server.permission.PermissionManagerLocal
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+
+class PermissionManagerLocalImpl(
+ private val service: AccessCheckingService
+) : PermissionManagerLocal {
+ private val policy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+
+ override fun isSignaturePermissionAllowlistForceEnforced(): Boolean {
+ check(Build.isDebuggable())
+ return policy.isSignaturePermissionAllowlistForceEnforced
+ }
+
+ override fun setSignaturePermissionAllowlistForceEnforced(forceEnforced: Boolean) {
+ check(Build.isDebuggable())
+ policy.isSignaturePermissionAllowlistForceEnforced = forceEnforced
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f0dd75..e5d315358df6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@ public final class InputMethodManagerServiceTests {
@Test
public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
var writer = new StringWriter();
- var history = new InputMethodManagerService.SoftInputShowHideHistory();
- history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+ var history = new SoftInputShowHideHistory();
+ history.addEntry(new SoftInputShowHideHistory.Entry(
null,
null,
null,
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
index 64a9a3b4f119..a5011a8d8b00 100644
--- a/services/tests/VpnTests/Android.bp
+++ b/services/tests/VpnTests/Android.bp
@@ -17,8 +17,7 @@ android_test {
"java/**/*.java",
"java/**/*.kt",
],
-
- defaults: ["framework-connectivity-test-defaults"],
+ sdk_version: "core_platform", // tests can use @CorePlatformApi's
test_suites: ["device-tests"],
static_libs: [
"androidx.test.rules",
@@ -32,6 +31,13 @@ android_test {
"service-connectivity-tiramisu-pre-jarjar",
],
libs: [
+ // order matters: classes in framework-connectivity are resolved before framework,
+ // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+ // stubs in framework
+ "framework-connectivity.impl",
+ "framework-connectivity-t.impl",
+ "framework",
+ "framework-res",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 000000000000..1f3184d056f2
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+ private val mockHbmController = mock<HighBrightnessModeController>()
+ private val mockCallback = mock<Runnable>()
+ private val mockConfig = mock<DisplayDeviceConfig>()
+ private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+ private val mockHdrClamper = mock<HdrClamper>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockToken = mock<IBinder>()
+
+ @Test
+ fun `returns HBC max brightness if HBM supported and ON`() {
+ val controller = createController()
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns NBC max brightness if device does not support HBM`() {
+ val controller = createController(hbmSupported = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns NBC max brightness if HBM not allowed`() {
+ val controller = createController(hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns HBC max brightness if NBM is disabled`() {
+ val controller = createController(nbmEnabled = false, hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns HBC max brightness if lower than NBC max brightness`() {
+ val controller = createController(
+ hbmAllowed = false,
+ hbmMaxBrightness = TRANSITION_POINT,
+ nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+ )
+ assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+ }
+
+ 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)
+ whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+ return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+ mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+ DisplayDeviceInfo())
+ }
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1981c0..1c71abc984df 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@ public class DisplayDeviceTest {
private static final int WIDTH = 500;
private static final int HEIGHT = 900;
private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+ private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
@Mock
private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@ public class DisplayDeviceTest {
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ PORTRAIT_DOUBLE_WIDTH);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -84,6 +96,17 @@ public class DisplayDeviceTest {
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ LANDSCAPE_DOUBLE_HEIGHT);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -111,8 +134,14 @@ public class DisplayDeviceTest {
private final DisplayDeviceInfo mDisplayDeviceInfo;
FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+ this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+ }
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+ boolean isAnisotropyCorrectionEnabled) {
super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
- InstrumentationRegistry.getInstrumentation().getContext());
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ isAnisotropyCorrectionEnabled);
mDisplayDeviceInfo = displayDeviceInfo;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b142334db9e9..48fc4078999d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -106,6 +106,7 @@ import android.os.SystemProperties;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -123,6 +124,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -204,6 +207,8 @@ public class DisplayManagerServiceTest {
@Rule
public SetFlagsRule mSetFlagsRule =
new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
private Context mContext;
@@ -376,6 +381,8 @@ public class DisplayManagerServiceTest {
when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
mContext = spy(new ContextWrapper(
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+ final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
mResources = Mockito.spy(mContext.getResources());
mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
@@ -2408,6 +2415,7 @@ public class DisplayManagerServiceTest {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2440,6 +2448,7 @@ public class DisplayManagerServiceTest {
.when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2487,6 +2496,7 @@ public class DisplayManagerServiceTest {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -2652,6 +2662,7 @@ public class DisplayManagerServiceTest {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
DisplayManagerInternal localService = displayManager.new LocalService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2699,6 +2710,7 @@ public class DisplayManagerServiceTest {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
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 76b77808b880..fb2321316e00 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1927,6 +1927,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);
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1939,7 +1941,8 @@ public final class DisplayPowerControllerTest {
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ hysteresisLevels, screenOffBrightnessSensorController,
+ hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -2027,6 +2030,8 @@ public final class DisplayPowerControllerTest {
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
+
private final HdrClamper mHdrClamper;
private final BrightnessClamperController mClamperController;
@@ -2040,6 +2045,7 @@ public final class DisplayPowerControllerTest {
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper,
BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
@@ -2051,6 +2057,7 @@ public final class DisplayPowerControllerTest {
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mHdrClamper = hdrClamper;
mClamperController = clamperController;
mFlags = flags;
@@ -2163,7 +2170,8 @@ public final class DisplayPowerControllerTest {
DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+ displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+ mFlags, displayToken, info);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1529a087c284..1a71e77a3b1b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -228,13 +228,27 @@ public class ExternalDisplayPolicyTest {
@Test
public void testOnExternalDisplayAvailable() {
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+
mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+
+ mExternalDisplayPolicy.onBootCompleted();
assertAskedToEnableDisplay();
verify(mMockedExternalDisplayStatsService).onDisplayConnected(eq(mMockedLogicalDisplay));
}
@Test
+ public void testOnExternalDisplayUnpluggedBeforeBootCompletes() {
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayDisconnected(anyInt());
+ }
+
+ @Test
public void testOnExternalDisplayAvailable_criticalThermalCondition()
throws RemoteException {
// Disallow external displays due to thermals.
@@ -303,8 +317,14 @@ public class ExternalDisplayPolicyTest {
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+ verify(mMockedLogicalDisplay).setEnabledLocked(false);
clearInvocations(mMockedLogicalDisplayMapper);
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ clearInvocations(mMockedLogicalDisplay);
+ }
+
+ private void assertNotAskedToEnableDisplay() {
+ verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+ verify(mMockedLogicalDisplay, never()).setEnabledLocked(anyBoolean());
}
private void assertIsExternalDisplayAllowed(final boolean enabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418f9276..549f0d74b67b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@ public class LogicalDisplayTest {
}
@Test
+ public void testLetterbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+ /*
+ * Content is too wide, should become letterboxed
+ * ______DISPLAY_WIDTH________
+ * | |
+ * |________________________|
+ * | |
+ * | CONTENT |
+ * | |
+ * |________________________|
+ * | |
+ * |________________________|
+ */
+ // Make a wide application content, by reducing its height.
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoLetterbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+ // correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testLetterbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = Surface.ROTATION_90;
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+ assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+ /*
+ * Content is too tall, should become pillarboxed
+ * ______DISPLAY_WIDTH________
+ * | | | |
+ * | | | |
+ * | | | |
+ * | | CONTENT | |
+ * | | | |
+ * | | | |
+ * |____|________________|____|
+ */
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ displayInfo.rotation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is a bit wider than in #testPillarbox, due to content added stretching
+ assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoPillarbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too tall, should have occupy the whole screen - but it won't because of
+ // anisotropy correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
public void testGetDisplayPosition() {
- Point expectedPosition = new Point();
+ Point expectedPosition = new Point(0, 0);
SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index bd20ae26821e..ce281daf41c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,6 +56,7 @@ import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Rule;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -160,6 +161,7 @@ public abstract class BaseBroadcastQueueTest {
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 66ab8076a217..1172685c4466 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -86,7 +86,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
@@ -2335,8 +2334,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
.isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
}
- @Ignore
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
public void testDeferOutgoingBroadcasts() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
@@ -2350,6 +2349,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
makeRegisteredReceiver(receiverGreenApp),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ // Verify that we invoke the call to freeze the caller app.
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
waitForIdle();
verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
index daa02111f71f..8337fd2453df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7f88b0031191..eb8950384f10 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -88,6 +88,7 @@ import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -1318,6 +1319,152 @@ public class AccessibilityManagerServiceTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+ mockManageAccessibilityGranted(mTestableContext);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
+ notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
+ List<ComponentName> tiles =
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mAccessibilityButtonTargets.clear();
+ userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+ .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).containsExactlyElementsIn(List.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
+ notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
+ Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
+ qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ qsTiles.stream().toList()
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).doesNotContain(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
return mockAccessibilityServiceInfo(
@@ -1367,6 +1514,11 @@ public class AccessibilityManagerServiceTest {
PackageManager.PERMISSION_GRANTED);
}
+ private void mockStatusBarServiceGranted(TestableContext context) {
+ context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
private void assertStartActivityWithExpectedComponentName(Context mockContext,
String componentName) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 52a5d8f6d952..b1964e23ff53 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -30,6 +30,8 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_E
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -45,6 +47,8 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.Color;
import android.platform.test.annotations.RequiresFlagsDisabled;
@@ -59,6 +63,7 @@ import android.view.Display;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
@@ -68,6 +73,9 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Map;
+import java.util.Set;
+
/** Tests for AccessibilityUserState */
public class AccessibilityUserStateTest {
@@ -431,7 +439,70 @@ public class AccessibilityUserStateTest {
assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked());
assertEquals(focusColorValue, mUserState.getFocusColorLocked());
+ }
+
+ @Test
+ public void updateA11yQsTargetLocked_valueUpdated() {
+ Set<String> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
+ );
+
+ mUserState.updateA11yQsTargetLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTargets_returnsCopiedData() {
+ updateA11yQsTargetLocked_valueUpdated();
+
+ Set<String> targets = mUserState.getA11yQsTargets();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+ }
+
+ @Test
+ public void updateA11yTilesInQsPanelLocked_valueUpdated() {
+ Set<ComponentName> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mUserState.updateA11yTilesInQsPanelLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTilesInQsPanel_returnsCopiedData() {
+ updateA11yTilesInQsPanelLocked_valueUpdated();
+
+ Set<ComponentName> targets = mUserState.getA11yQsTilesInQsPanel();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isNotEmpty();
+ }
+
+ @Test
+ public void getTileServiceToA11yServiceInfoMapLocked() {
+ final ComponentName tileComponent =
+ new ComponentName(COMPONENT_NAME.getPackageName(), "FakeTileService");
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = tileComponent.getPackageName();
+ serviceInfo.name = COMPONENT_NAME.getClassName();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ when(mMockServiceInfo.getTileServiceName()).thenReturn(tileComponent.getClassName());
+ when(mMockServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
+ mUserState.mInstalledServices.add(mMockServiceInfo);
+ mUserState.updateTileServiceMapForAccessibilityServiceLocked();
+
+ Map<ComponentName, AccessibilityServiceInfo> actual =
+ mUserState.getTileServiceToA11yServiceInfoMapLocked();
+ assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
}
private int getSecureIntForUser(String key, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e2374c..f6dc2f0f05b2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@ package com.android.server.accessibility;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
}
@Test
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -661,7 +745,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -710,12 +794,12 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -771,11 +855,11 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -979,7 +1063,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@@ -1001,7 +1085,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@@ -1019,7 +1103,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1173,8 +1257,6 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
final WindowInfo windowInfo = WindowInfo.obtain();
- // TODO(b/325341171): add tests with various kinds of windows such as
- // changing window types, touchable or not, trusted or not, etc.
windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
windowInfo.token = windowToken.asBinder();
@@ -1235,16 +1317,16 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1280,4 +1362,27 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ffa5dde..5582e13cbb4d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigrationUnAffiliated_skipped() throws Exception {
prepareAdmin1AsDo();
prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 26cda65309ad..99ab40569b70 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -325,7 +325,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -6223,6 +6222,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testSensitiveAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r1.getLifespanMs(anyLong())).thenReturn(1);
+
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addEnqueuedNotification(r1);
+
+ // Test an adjustment for an enqueued notification
+ Bundle signals = new Bundle();
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+ r1.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+ assertTrue(mService.checkLastSensitiveLog(false, true, 1));
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r2 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r2.getLifespanMs(anyLong())).thenReturn(2);
+
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+ assertTrue(mService.checkLastSensitiveLog(true, true, 2));
+
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, false);
+ Adjustment adjustment3 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+ assertTrue(mService.checkLastSensitiveLog(true, false, 2));
+ }
+
+ @Test
public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
@@ -13180,35 +13225,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void fixNotification_customAllowlistToken()
- throws Exception {
- Notification n = new Notification.Builder(mContext, "test")
- .build();
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- allowlistToken.set(n, new Binder());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
-
- IBinder actual = null;
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- actual = (IBinder) allowlistToken.get(n);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- assertTrue(mService.ALLOWLIST_TOKEN == actual);
- }
-
- @Test
public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
.thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 6976ec3b0465..07d25dfd814e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -45,6 +45,13 @@ public class TestableNotificationManagerService extends NotificationManagerServi
ComponentPermissionChecker permissionChecker;
+ private static class SensitiveLog {
+ public boolean hasPosted;
+ public boolean hasSensitiveContent;
+ public long lifetime;
+ }
+ public SensitiveLog lastSensitiveLog = null;
+
TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
InstanceIdSequence notificationInstanceIdSequence) {
super(context, logger, notificationInstanceIdSequence);
@@ -167,6 +174,15 @@ public class TestableNotificationManagerService extends NotificationManagerServi
return permissionChecker.check(permission, uid, owningUid, exported);
}
+ @Override
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted, boolean hasSensitiveContent,
+ int lifetimeMs) {
+ lastSensitiveLog = new SensitiveLog();
+ lastSensitiveLog.hasPosted = hasPosted;
+ lastSensitiveLog.hasSensitiveContent = hasSensitiveContent;
+ lastSensitiveLog.lifetime = lifetimeMs;
+ }
+
public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
private int mGetStrongAuthForUserReturnValue = 0;
StrongAuthTrackerFake(Context context) {
@@ -183,6 +199,15 @@ public class TestableNotificationManagerService extends NotificationManagerServi
}
}
+ public boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) {
+ if (lastSensitiveLog == null) {
+ return false;
+ }
+ return hasPosted == lastSensitiveLog.hasPosted
+ && hasSensitive == lastSensitiveLog.hasSensitiveContent
+ && lifetime == lastSensitiveLog.lifetime;
+ }
+
public interface ComponentPermissionChecker {
int check(String permission, int uid, int owningUid, boolean exported);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb14bf3..10cfb5fdd922 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@ public class TaskTests extends WindowTestsBase {
}
@Test
+ public void testUserLeaving() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ mSupervisor.mUserLeaving = true;
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.sleepIfPossible(false /* shuttingDown */);
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+ clearInvocations(task);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.setPausingActivity(null);
+ doReturn(false).when(task).canBeResumed(any());
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
+ @Test
public void testSwitchUser() {
final Task rootTask = createTask(mDisplayContent);
final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1823,6 +1840,66 @@ public class TaskTests extends WindowTestsBase {
}
@Test
+ public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = task.getSyncTransaction();
+ final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+ spyOn(unembeddedActivity);
+ spyOn(fragment1);
+ spyOn(fragment2);
+
+ doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+ doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(true).when(fragment1).isVisible();
+
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed above all the windows when boosted and the cover
+ // surface should show.
+ task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(fragment2).assignLayer(t, 2);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed just above the owner TaskFragment and the cover
+ // surface should hide.
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+ task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+ verify(fragment2).assignLayer(t, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ }
+
+ @Test
public void testMoveTaskFragmentsToBottomIfNeeded() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dc504cac2e12..a35a35acb6c1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -153,7 +153,8 @@ public class UsageStatsService extends SystemService implements
= SystemProperties.getBoolean("persist.debug.time_correction", true);
private static final boolean USE_DEDICATED_HANDLER_THREAD =
- SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false);
+ SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+ Flags.useDedicatedHandlerThread());
static final boolean DEBUG = false; // Never submit with true
static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bbf1462..df32fbd2e58d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@ public class CarrierConfigManager {
"support_no_reply_timer_for_cfnry_bool";
/**
+ * No reply time value to be sent to network for call forwarding on no reply
+ * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+ * Controls time in seconds for the no reply condition on in the call forwarding
+ * settings UI.
+ * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+ *
+ * @hide
+ */
+ public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+ "no_reply_timer_for_cfnry_sec_int";
+
+ /**
* List of the FAC (feature access codes) to dial as a normal call.
* @hide
*/
@@ -10666,6 +10678,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+ sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b3a37c..c3495996a231 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@ oneway interface IQualifiedNetworksServiceCallback
{
void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
- void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+ void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d025c8..f775de6ebef4 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@ public abstract class QualifiedNetworksService extends Service {
@AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
if (mCallback != null) {
try {
- mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+ mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
} catch (RemoteException e) {
loge("Failed to call onReconnectQualifiedNetworkType. " + e);
}
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 443de8edc2d3..7cdab94534b0 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index c51da052f4bf..377288d4aa2d 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index ab23401bf629..68b147338a1d 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index 8faf22440828..9f14b136861f 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -17,28 +17,25 @@
package com.android.input.screenshot
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
-class InputGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
- appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
- deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/input_screenshots",
- pathConfig = pathConfig,
- ) {
+/** A [GoldenPathManager] that should be used for all Input screenshot tests. */
+class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/input_screenshots",
+ pathConfig = pathConfig,
+ ) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "InputGoldenImagePathManager"
+ return "InputGoldenPathManager"
}
-} \ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41d3609..2f408964fd8c 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@ class InputScreenshotTestRule(
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- InputGoldenImagePathManager(
+ InputGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636aef7ed..75284c712bd2 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@ import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
-import android.util.LongArrayQueue;
import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;